mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-21 22:55:55 +00:00
refactor(agent-core-v2): rename scoped services to encode scope
- rename service interfaces and implementations to carry the Session/Agent scope prefix and Service suffix (e.g. IApprovalService -> ISessionApprovalService, ApprovalService -> SessionApprovalService) - rename LifecycleScope.Core to App in the DI base - update DI createDecorator keys to match the new identifiers - propagate the renames through server-v2 routes/transport, examples, docs, and the agent-core-dev skill
This commit is contained in:
parent
075262a49d
commit
cd47ef4aff
382 changed files with 2852 additions and 2814 deletions
|
|
@ -20,7 +20,7 @@ v1 is a **VSCode-style singleton container**: services self-register with `regis
|
|||
| Resolve SUT in tests | `ix.createInstance(Impl)` (common) | `ix.get(IX)` by interface — see test.md |
|
||||
| Scope tests | none | `createScopedTestHost` — see test.md |
|
||||
| Errors | `from '../../errors'` (central `KimiError`, `ErrorCodes`) | `from '#/_base/errors'` + domain co-located `XxxError` — see errors.md |
|
||||
| Flags | `flags/` (process-global `FlagResolver`) | `flag/` (Core-scope `IFlagService`) — see flags.md |
|
||||
| Flags | `flags/` (process-global `FlagResolver`) | `flag/` (App-scope `IFlagService`) — see flags.md |
|
||||
| Permission | `agent/permission/` (hardcoded chain) | `permission*` (registry + composer) — see permission.md |
|
||||
|
||||
## The align workflow
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ 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 Core scope.
|
||||
- `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/config/thinking.ts` — `resolveThinkingEffort` / `resolveThinkingLevel` helpers (own a local `ThinkingConfigDefaults` structural type; do not import `profile`).
|
||||
- `src/config/configPure.ts` — `isPlainObject`, `deepMerge`, `omitUndefined`, `describeUnknownError`.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ A Service = a bundle of **state** + a set of **behaviors**, bound to a **lifetim
|
|||
|
||||
| Scope | State identity (keyed by) | Lifetime |
|
||||
|---|---|---|
|
||||
| `Core` | none (single global instance) | the process |
|
||||
| `App` | none (single global instance) | the process |
|
||||
| `Session` | `sessionId` | one session |
|
||||
| `Agent` | `agentId` | one agent |
|
||||
| `Turn` | `turnId` | one turn |
|
||||
|
|
@ -33,7 +33,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 → **`Core`**
|
||||
- one global instance → **`App`**
|
||||
- one per session → **`Session`**
|
||||
- one per agent → **`Agent`**
|
||||
- one per turn → **`Turn`**
|
||||
|
|
@ -41,13 +41,13 @@ A Service = a bundle of **state** + a set of **behaviors**, bound to a **lifetim
|
|||
|
||||
**Q3 (stateless). What is the shortest-lived dependency it must inject?**
|
||||
|
||||
A stateless Service is pulled *down* by its shortest-lived dependency: if it injects an `Agent`-scoped Service, it cannot be `Core`. Among the scopes that still satisfy every dependency, **default to the longest-lived one** (usually `Core`) to maximize reuse. Push it down only when it must inject a shorter-lived Service, or when you want to limit its visibility.
|
||||
A stateless Service is pulled *down* by its shortest-lived dependency: if it injects an `Agent`-scoped Service, it cannot be `App`. Among the scopes that still satisfy every dependency, **default to the longest-lived one** (usually `App`) to maximize reuse. Push it down only when it must inject a shorter-lived Service, or when you want to limit its visibility.
|
||||
|
||||
### The core anti-pattern (a litmus test)
|
||||
|
||||
> **Do not store per-session state in a `Map<sessionId, …>` inside a `Core` Service.**
|
||||
> **Do not store per-session state in a `Map<sessionId, …>` inside a `App` Service.**
|
||||
|
||||
This is the tell-tale sign of "should have been `Session`-scoped but was parked at `Core`". Consequences: nobody cleans the entry up when the session ends (leak); every consumer threads `sessionId` around (loss of type safety); it cannot inject `Session`/`Agent`-scoped collaborators.
|
||||
This is the tell-tale sign of "should have been `Session`-scoped but was parked at `App`". Consequences: nobody cleans the entry up when the session ends (leak); every consumer threads `sessionId` around (loss of type safety); it cannot inject `Session`/`Agent`-scoped collaborators.
|
||||
|
||||
### One-sentence self-check
|
||||
|
||||
|
|
@ -71,19 +71,19 @@ The standard split is "global registry / factory" + "per-instance":
|
|||
|
||||
| Tier | Role | Naming tends to |
|
||||
|---|---|---|
|
||||
| `Core` | global registry / catalog / factory — knows "all of them" and how to create one | `XxxStore` / `XxxRegistry` / `XxxCatalog` |
|
||||
| `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` |
|
||||
|
||||
Canonical splits in the codebase:
|
||||
|
||||
- **`records`** — `ISessionStore` (`Core`) + `ISessionMetaStore` (`Session`) + `IAgentRecords` (`Agent`).
|
||||
- **`config`** — `IConfigRegistry` / `IConfigService` (`Core`).
|
||||
- **`kosong`** — `IProtocolHandlerRegistry` (`Core`) + `IProviderManager` (`Session`). Generation is driven by `ILLMRequester` (`Agent`) in the `llmRequester` domain.
|
||||
- **`tool`** — `IToolDefinitionRegistry` (`Core`) + `IToolService` (`Agent`).
|
||||
- **`records`** — `ISessionStore` (`App`) + `ISessionMetaStore` (`Session`) + `IAgentRecords` (`Agent`).
|
||||
- **`config`** — `IConfigRegistry` / `IConfigService` (`App`).
|
||||
- **`kosong`** — `IProtocolHandlerRegistry` (`App`) + `IProviderManager` (`Session`). Generation is driven by `ILLMRequester` (`Agent`) in the `llmRequester` domain.
|
||||
- **`tool`** — `IToolDefinitionRegistry` (`App`) + `IToolService` (`Agent`).
|
||||
|
||||
Split when the domain genuinely has both a global view and per-instance state. Do **not** split when state lives at only one lifetime (e.g. purely `Core` like `log`; purely `Agent` like `prompt`). Do not pre-split for symmetry.
|
||||
Split when the domain genuinely has both a global view and per-instance state. Do **not** split when state lives at only one lifetime (e.g. purely `App` like `log`; purely `Agent` like `prompt`). Do not pre-split for symmetry.
|
||||
|
||||
After the split, the `Core` Service usually plays the **factory**; most consumers inject the **per-instance** Service. Inject the `Core` factory only when you genuinely need cross-instance management.
|
||||
After the split, the `App` Service usually plays the **factory**; most consumers inject the **per-instance** Service. Inject the `App` factory only when you genuinely need cross-instance management.
|
||||
|
||||
## 4. Choosing a calling style
|
||||
|
||||
|
|
@ -125,10 +125,10 @@ The three mechanisms above are also where a domain accepts new behavior without
|
|||
|
||||
| Need | Extension point | Typical scope |
|
||||
|---|---|---|
|
||||
| Register a new implementation / definition | a **registry / catalog** the domain queries | `Core` |
|
||||
| Register a new implementation / definition | a **registry / catalog** the domain queries | `App` |
|
||||
| React to a fact the domain announces | an **event** on the bus | the announcing scope |
|
||||
| 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) | `Core` (composition root) |
|
||||
| Swap a backend (File ↔ DB ↔ S3) | a **Store / Storage token** at the byte layer (see persistence.md) | `App` (composition root) |
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ domain: `<name>` (owning scope: <Scope>)
|
|||
│ ├─ (inject) <ConsumerDomain> @<Scope> — <what they use me for>
|
||||
│ └─ (accessor) <ConsumerDomain> @<Scope> — <what they use me for>
|
||||
├─ exposes (interfaces I provide, by scope)
|
||||
│ ├─ Core : <IXxxRegistry> — <role>
|
||||
│ ├─ App : <IXxxRegistry> — <role>
|
||||
│ ├─ Session : <ISessionXxx> — <role>
|
||||
│ ├─ Agent : <IAgentXxx> — <role>
|
||||
│ └─ Turn : — — (none)
|
||||
|
|
@ -208,7 +208,7 @@ Conventions:
|
|||
When a domain has `accessor` consumers, draw the reverse-direction borrow next to the tree so it is never mistaken for injection:
|
||||
|
||||
```text
|
||||
Core scope
|
||||
App scope
|
||||
<AncestorService> ──holds──► IScopeHandle(<id>)
|
||||
│
|
||||
│ accessor.get(<IMyService>)
|
||||
|
|
@ -230,10 +230,10 @@ domain: `session` (owning scope: Session)
|
|||
├─ serves (who uses me)
|
||||
│ ├─ (inject) — (none yet)
|
||||
│ └─ (accessor)
|
||||
│ ├─ session-lifecycle @Core — archive() before disposing the child scope
|
||||
│ └─ gateway / rpc @Core(edge) — session-level commands (archive, rename…)
|
||||
│ ├─ session-lifecycle @App — archive() before disposing the child scope
|
||||
│ └─ gateway / rpc @App(edge) — session-level commands (archive, rename…)
|
||||
├─ exposes (interfaces I provide, by scope)
|
||||
│ ├─ Core : — — (no global session state here)
|
||||
│ ├─ App : — — (no global session state here)
|
||||
│ ├─ Session : ISessionService — this session's operations + child-agent set
|
||||
│ ├─ Agent : — — (per-agent state lives in agent-lifecycle)
|
||||
│ └─ Turn : — — (no per-turn session state)
|
||||
|
|
@ -242,13 +242,13 @@ domain: `session` (owning scope: Session)
|
|||
├─ agent-lifecycle @Session direct — drives child-agent lifecycle
|
||||
├─ sessionMetaStore @Session direct — persists session metadata
|
||||
├─ session-activity @Session direct — records activity
|
||||
└─ event @Core direct — broadcasts session-level facts
|
||||
└─ event @App direct — broadcasts session-level facts
|
||||
```
|
||||
|
||||
Cross-scope borrow for `session`:
|
||||
|
||||
```text
|
||||
Core scope
|
||||
App scope
|
||||
SessionLifecycleService ──holds──┐
|
||||
GatewayService ───────────holds──┼──► IScopeHandle(sessionId)
|
||||
│
|
||||
|
|
@ -271,8 +271,8 @@ For a multi-scope split, the `exposes` block fills more than one scope — see t
|
|||
|
||||
- Scope is not a domain; ownership follows write authority and invariants, not read consumption.
|
||||
- Do not create `I{Scope}EntityService` bundles (`IAgentEntityService`, `ISessionEntityService`, `ITurnEntityService`) that re-merge multiple domains.
|
||||
- No `Map<sessionId, …>` at `Core` to fake per-session state.
|
||||
- Scope follows state identity; stateless Services are pulled down by their shortest-lived dependency, otherwise default to `Core`.
|
||||
- No `Map<sessionId, …>` at `App` to fake per-session state.
|
||||
- Scope follows state identity; stateless Services are pulled down by their shortest-lived dependency, otherwise default to `App`.
|
||||
- Do not pre-split a domain that has state at only one lifetime.
|
||||
- Need a result / I orchestrate → direct call; stating a fact → event; ordered participation / may veto → hook.
|
||||
- Foundational layers never know upstream ones; business code never depends on the edge layer.
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ The `session` domain owns only Session-level identity, metadata, lifecycle comma
|
|||
|---|---|---|
|
||||
| `sessionId`, `workspaceId`, `sessionDir`, `metaScope` | `session-context` | Seeded facts; no IO |
|
||||
| `SessionMeta` | `session-metadata` | Durable atomic document; entity-like |
|
||||
| Open session scope registry | `session-lifecycle` | Core-scope live handles; not the persisted entity table |
|
||||
| Open session scope registry | `session-lifecycle` | App-scope live handles; not the persisted entity table |
|
||||
| Session commands such as `archive()` | `session` | Orchestrates metadata, agent teardown, and events |
|
||||
| Persisted session list / get / count | `session-index` | Backend-neutral read model |
|
||||
| Running / idle / awaiting status | `session-activity` | Derived from interactions and active turns; owns no state |
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
# Topic — Flags
|
||||
|
||||
Experimental feature-flag gating for agent-core-v2 — a Core-scope `IFlagService` resolver plus a writable `IFlagRegistry` catalog that domains contribute their flags to, backed by the `[experimental]` config section.
|
||||
Experimental feature-flag gating for agent-core-v2 — a App-scope `IFlagService` resolver plus a writable `IFlagRegistry` catalog that domains contribute their flags to, backed by the `[experimental]` config section.
|
||||
|
||||
Gate not-yet-public features behind `IFlagService.enabled(id)`, per the repository hard rule that unreleased behavior must be flag-gated. v1 was a process-global `FlagResolver` singleton over a central `FLAG_DEFINITIONS` array; v2 is a scoped DI service whose flag definitions are registered **decentrally** by each owning domain — there is no central catalog to edit.
|
||||
|
||||
## 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; Core scope.
|
||||
- `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 Core scope.
|
||||
- `src/flag/flagService.ts` — `FlagService` impl + `MASTER_ENV` (`KIMI_CODE_EXPERIMENTAL_FLAG`) + `EXPERIMENTAL_SECTION` (`experimental`); reads definitions from `IFlagRegistry`; self-registers at App scope.
|
||||
- `src/flag/index.ts` — barrel; re-exported by `src/index.ts` at the L3 block.
|
||||
- `src/<domain>/flag.ts` — each domain that owns a flag declares it here and calls `registerFlagDefinition` at the module top level (e.g. `src/microCompaction/flag.ts`). The directory already names the domain, so the file is just `flag.ts`.
|
||||
|
||||
## Public surface
|
||||
|
||||
- `IFlagService` (DI token, Core scope): `enabled(id)`, `explain(id)`, `snapshot()`, `enabledIds()`, `explainAll()`, `setConfigOverrides(overrides)`, `registry`.
|
||||
- `IFlagRegistry` (DI token, Core scope): `register(definition)`, `get(id)`, `list()` — writable catalog. `register` is the **runtime** path (tests, dynamic registration); `IFlagService.registry` exposes the same instance for hosts/UI to enumerate flags without resolving them.
|
||||
- `IFlagService` (DI token, App scope): `enabled(id)`, `explain(id)`, `snapshot()`, `enabledIds()`, `explainAll()`, `setConfigOverrides(overrides)`, `registry`.
|
||||
- `IFlagRegistry` (DI token, App scope): `register(definition)`, `get(id)`, `list()` — writable catalog. `register` is the **runtime** path (tests, dynamic registration); `IFlagService.registry` exposes the same instance for hosts/UI to enumerate flags without resolving them.
|
||||
- `registerFlagDefinition(definition)` — the **import-time** path. Domains call this from their `flag.ts` top level; contributions are queued and drained by `FlagRegistryService` when it is instantiated.
|
||||
- `FlagService` / `FlagRegistryService`: exported for tests and hosts that construct them directly.
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export class Greeter implements IGreeter {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core, // lifetime: process-wide
|
||||
LifecycleScope.App, // lifetime: process-wide
|
||||
IGreeter, // identity
|
||||
Greeter, // implementation
|
||||
InstantiationType.Eager, // when to construct: immediately
|
||||
|
|
@ -130,10 +130,10 @@ export class WSBroadcastService extends Disposable implements IWSBroadcastServic
|
|||
|
||||
```ts
|
||||
// Eager: constructed when the scope is created
|
||||
registerScopedService(LifecycleScope.Core, ILogService, LogService, InstantiationType.Eager, 'log');
|
||||
registerScopedService(LifecycleScope.App, ILogService, LogService, InstantiationType.Eager, 'log');
|
||||
|
||||
// Delayed: constructed on first get
|
||||
registerScopedService(LifecycleScope.Core, IScopeRegistry, ScopeRegistry, InstantiationType.Delayed, 'gateway');
|
||||
registerScopedService(LifecycleScope.App, IScopeRegistry, ScopeRegistry, InstantiationType.Delayed, 'gateway');
|
||||
```
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Classes talk only to interfaces and never care how an implementation is construc
|
|||
Lifetimes form a tree, from longest to shortest:
|
||||
|
||||
```text
|
||||
Core (0) process-wide, single global instance
|
||||
App (0) process-wide, single global instance
|
||||
└── Session (1) one session
|
||||
└── Agent (2) one agent
|
||||
└── Turn (3) one turn of conversation
|
||||
|
|
@ -25,7 +25,7 @@ Core (0) process-wide, single global instance
|
|||
|
||||
```ts
|
||||
export enum LifecycleScope {
|
||||
Core = 0,
|
||||
App = 0,
|
||||
Session = 1,
|
||||
Agent = 2,
|
||||
Turn = 3,
|
||||
|
|
@ -40,8 +40,8 @@ export enum LifecycleScope {
|
|||
|
||||
A child scope sees its ancestors; a parent never sees its children. Resolution walks *up* the tree:
|
||||
|
||||
- ✅ A `Turn` service injects a `Session` or `Core` service (found upward).
|
||||
- ❌ A `Core` service injects a `Session` service (the parent does not look down, and the child may not exist yet).
|
||||
- ✅ A `Turn` service injects a `Session` or `App` service (found upward).
|
||||
- ❌ An `App` service injects a `Session` service (the parent does not look down, and the child may not exist yet).
|
||||
|
||||
> **Short-lived may inject long-lived; never the reverse.** The tree structure enforces this — it is not a matter of discipline.
|
||||
|
||||
|
|
@ -55,19 +55,33 @@ Deterministic: **child scopes die first; within one scope, instances dispose in
|
|||
|
||||
- **Header only.** Comments live solely in the top-of-file `/** */` block — never beside functions, methods, or statements. The code is the source of truth for *how*; the header states *what the module exposes and the responsibility it owns*.
|
||||
- **Identity line first.** Start with `` `<domain>` domain (Ln) — <one-line role>. `` Keep an existing `(cross-cutting)` label as-is; barrels omit the layer (`` `<domain>` domain barrel — … ``). Write the role as a responsibility ("drives the turn lifecycle"), not a symbol list.
|
||||
- **Impl files** add collaborators + scope: list every imported cross-domain collaborator as a role ("persists records through `records`"); read scope from `registerScopedService(LifecycleScope.X, …)`.
|
||||
- **Contract files** add the public contract + scope.
|
||||
- **Scope is in the filename.** `session*.ts` = Session, `agent*.ts` = Agent, no prefix = App (see service-authoring.md). State the same scope in the header so the two never drift.
|
||||
- **Interface files** (`<name>.ts`) state the public contract + scope: which `IXxx` they define and what it is for.
|
||||
- **Impl files** (`<name>Service.ts`) add collaborators + scope: list every imported cross-domain collaborator as a role ("persists records through `records`"); read scope from `registerScopedService(LifecycleScope.X, …)`.
|
||||
- **Contribution files** (`<targetDomain>.ts` / `<what>.contrib.ts`) state what they register into the target domain (e.g. "registers the `log` config section into `config`").
|
||||
- **Pure-function / `.types` / `.errors` files** state the responsibility only — they own no scoped state, so no scope line.
|
||||
|
||||
Impl example:
|
||||
Impl file example (`sessionService.ts`):
|
||||
|
||||
```ts
|
||||
/**
|
||||
* `session` domain (L6) — `ISessionService` implementation.
|
||||
*
|
||||
* Owns the session's child-agent set and session-level operations; drives
|
||||
* agent lifecycle through `agent-lifecycle`, broadcasts through `event`,
|
||||
* agent lifecycle through `agentLifecycle`, broadcasts through `event`,
|
||||
* persists session metadata through `records`, and records activity through
|
||||
* `session-activity`. Bound at Session scope.
|
||||
* `sessionActivity`. Bound at Session scope.
|
||||
*/
|
||||
```
|
||||
|
||||
Contribution file example (`config.ts` inside `log/`):
|
||||
|
||||
```ts
|
||||
/**
|
||||
* `log` domain — registers the `log` config section into `config`.
|
||||
*
|
||||
* Owns the `log` section schema and its env overlay; imported for the
|
||||
* registration side effect. Bound at App scope.
|
||||
*/
|
||||
```
|
||||
|
||||
|
|
@ -75,9 +89,9 @@ Barrel example:
|
|||
|
||||
```ts
|
||||
/**
|
||||
* `session` domain barrel — re-exports the session facade contract
|
||||
* (`session`) and its scoped service (`sessionService`). Importing this
|
||||
* barrel registers the `ISessionService` binding into the scope registry.
|
||||
* `session` domain barrel — re-exports the session contract (`session`),
|
||||
* its scoped service (`sessionService`), and its contribution files. Importing
|
||||
* this barrel registers the `ISessionService` binding into the scope registry.
|
||||
*/
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ interface PermissionPolicyEntry {
|
|||
factory: (accessor: ServicesAccessor) => PermissionPolicy;
|
||||
}
|
||||
|
||||
// Core scope — collects every domain's registration
|
||||
// App scope — collects every domain's registration
|
||||
interface IPermissionPolicyRegistry {
|
||||
register(entry: PermissionPolicyEntry): IDisposable;
|
||||
list(): readonly PermissionPolicyEntry[];
|
||||
|
|
|
|||
|
|
@ -4,33 +4,57 @@ How to write a Service in `packages/agent-core-v2`: file layout, naming, what go
|
|||
|
||||
## File layout
|
||||
|
||||
One folder per domain, **kebab-case**: `session/`, `session-activity/`, `contextMemory/`. Inside:
|
||||
One folder per domain, **camelCase**: `session/`, `sessionActivity/`, `contextMemory/`, `toolDedup/`. Inside, six kinds of files, all flat (no subdirectories):
|
||||
|
||||
```text
|
||||
<domain>/
|
||||
├── <domain>.ts ← contract: model types + interface(s) + decorator(s) + helpers
|
||||
├── <domain>Service.ts ← impl: class(es) + top-level registerScopedService(...)
|
||||
└── index.ts ← barrel: re-exports contract + impl (+ helpers)
|
||||
├── <name>.ts ← interface file: exactly one IXxx + its createDecorator + the types it owns
|
||||
├── <name>Service.ts ← impl file: exactly one class + exactly one registerScopedService(...)
|
||||
├── <concern>.ts ← pure function(s): no Service suffix, no class, no registration
|
||||
├── <targetDomain>.ts ← contribution file (common): registers into another domain's extension point
|
||||
├── <what>.contrib.ts ← contribution file (uncommon / ad-hoc)
|
||||
├── <domain>.types.ts ← shared types that no single interface owns
|
||||
└── index.ts ← barrel: re-exports everything; importing it runs the domain's registrations
|
||||
```
|
||||
|
||||
A domain may have more than one impl file when its Services live at different scopes or carry independent responsibilities (e.g. `logService.ts` for the Core `ILogService`, `sessionLogService.ts` for the Session `ISessionLogService`). See [Multi-Service domains](#multi-service-domains).
|
||||
- **One service per file.** An interface file holds exactly one injectable interface; an impl file holds exactly one class and exactly one `registerScopedService(...)`. A multi-service domain splits into one interface-file + one impl-file per service.
|
||||
- **Scope is in the filename.** `session*.ts` = Session, `agent*.ts` = Agent, no scope prefix = App (see [Naming](#naming)). The header comment restates the same scope.
|
||||
- A domain therefore has as many impl files as it has services (e.g. `logService.ts` for the App `ILogService`, `sessionLogService.ts` for the Session `ISessionLogService`). See [Multi-Service domains](#multi-service-domains).
|
||||
|
||||
The package entry `src/index.ts` re-exports each domain barrel so that importing the package runs every registration side effect.
|
||||
|
||||
## Naming
|
||||
|
||||
### Interfaces and classes
|
||||
|
||||
| Artifact | Rule | Example |
|
||||
|---|---|---|
|
||||
| Interface | `I` + PascalCase + `Service` suffix | `ISessionService`, `ILogService` |
|
||||
| Class | PascalCase + `Service` suffix, `implements` the interface | `SessionService implements ISessionService` |
|
||||
| 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<ISessionService>('sessionService')` |
|
||||
| Contract file | `<domain>.ts` (kebab domain, no `Service` suffix) | `session.ts`, `session-activity.ts` |
|
||||
| Impl file | `<domain>Service.ts` (with `Service` suffix) | `sessionService.ts` |
|
||||
| Interface | `I` + scope prefix + PascalCase domain + role suffix. Scope prefix: `Session` / `Agent` / none (= App). Role suffix is usually `Service`. | `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 `Service` suffix is the norm for injectables. Roles (facade / bus / broker / adapter) are conveyed by the interface shape and the file-header comment, not by inventing new suffixes.
|
||||
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.
|
||||
|
||||
Do not name a Service after a scope or a god-object-shaped data bag. `IAgentEntityService`, `IAgentDataService`, `ISessionEntityService`, and `ITurnEntityService` re-merge domains by lifetime; name Services after the real owning domain (`IBackgroundTaskEntityService`, `ISessionMetadata`, `IPermissionRulesService`). See [domain-boundaries.md](domain-boundaries.md).
|
||||
> Do **not** use the scope prefix to re-merge domains by lifetime. `IAgentEntityService`, `IAgentDataService`, `ISessionEntityService`, and `ITurnEntityService` 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).
|
||||
|
||||
### File names
|
||||
|
||||
File names derive from the interface / class names so that scope and role are visible in the tree:
|
||||
|
||||
| File kind | Rule | Example (interface → file) |
|
||||
|---|---|---|
|
||||
| Interface file | interface name minus leading `I`, minus trailing `Service` if present; acronym-aware lowerCamelCase | `ISessionLogService` → `sessionLog.ts`; `IAppendLogStore` → `appendLogStore.ts`; `ILogService` → `log.ts` |
|
||||
| Impl file | the class name; acronym-aware lowerCamelCase | `SessionLogService` → `sessionLogService.ts`; `AppendLogStoreService` → `appendLogStoreService.ts` |
|
||||
| Pure-function file | the function / concern name; no `Service` suffix | `formatLogEntry.ts`, `levelEnabled.ts` |
|
||||
| Contribution file (common) | the **target** domain name | `config.ts` (registers a config section), `tool.ts`, `flag.ts` |
|
||||
| Contribution file (uncommon) | `<what>.contrib.ts` | `slackWebhook.contrib.ts` |
|
||||
| Shared-types file | `<domain>.types.ts` | `log.types.ts` |
|
||||
| Errors file | `<name>.errors.ts` | `appendLogStore.errors.ts` |
|
||||
|
||||
Acronym-aware lowerCamelCase lowercases a leading acronym as a group: `ILLMRequester` → `llmRequester.ts`, `IWSGateway` → `wsGateway.ts`, `IOAuthToolkit` → `oauthToolkit.ts`, `IAgentRPCService` → `agentRpcService.ts`.
|
||||
|
||||
Because the impl class always ends in `Service` and the interface file never does, the two files of one service never collide — even for `Store` / `Registry` / `Resolver` interfaces (`IAppendLogStore` → `appendLogStore.ts` + `appendLogStoreService.ts`).
|
||||
|
||||
## The contract file (`<domain>.ts`)
|
||||
|
||||
|
|
@ -111,7 +135,7 @@ Holds the concrete class(es) and the top-level registration. A typical impl:
|
|||
/**
|
||||
* `greet` domain (Ln) — `IGreeter` implementation.
|
||||
*
|
||||
* … collaborators as roles ("logs through `log`") … Bound at Core scope.
|
||||
* … collaborators as roles ("logs through `log`") … Bound at App scope.
|
||||
*/
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
|
|
@ -131,7 +155,7 @@ export class Greeter implements IGreeter {
|
|||
}
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.Core, IGreeter, Greeter, InstantiationType.Eager, 'greet');
|
||||
registerScopedService(LifecycleScope.App, IGreeter, Greeter, InstantiationType.Eager, 'greet');
|
||||
```
|
||||
|
||||
What belongs here:
|
||||
|
|
@ -290,7 +314,7 @@ export class Greeter implements IGreeter {
|
|||
hello(): Greeting { return { message: 'hi' }; }
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.Core, IGreeter, Greeter, InstantiationType.Eager, 'greet');
|
||||
registerScopedService(LifecycleScope.App, IGreeter, Greeter, InstantiationType.Eager, 'greet');
|
||||
```
|
||||
|
||||
```ts
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ Telemetry is a **layer-1 root** domain (alongside `log`): pure `Core` scope, sta
|
|||
## Where things live
|
||||
|
||||
- `src/telemetry/telemetry.ts`: contract — `ITelemetryService` (facade), `ITelemetryAppender` (destination), `TelemetryProperties`, `nullTelemetryAppender`, and `TelemetryServiceOptions`.
|
||||
- `src/telemetry/telemetryService.ts`: `TelemetryService` impl + `registerScopedService(LifecycleScope.Core, …)`.
|
||||
- `src/telemetry/telemetryService.ts`: `TelemetryService` impl + `registerScopedService(LifecycleScope.App, …)`.
|
||||
- `src/telemetry/consoleAppender.ts`: `ConsoleAppender` — echoes events to a log function (dev / debug).
|
||||
- `src/telemetry/cloudAppender.ts`: `CloudAppender` — batches + enriches + posts to the telemetry endpoint.
|
||||
- `src/telemetry/cloudTransport.ts`: `CloudTransport` — HTTP transport behind `CloudAppender`.
|
||||
|
|
@ -59,7 +59,7 @@ Built-in appenders:
|
|||
|
||||
### Registering appenders (bootstrap)
|
||||
|
||||
Appenders are added after the Core scope exists, by resolving the service and calling `addAppender`:
|
||||
Appenders are added after the App scope exists, by resolving the service and calling `addAppender`:
|
||||
|
||||
```ts
|
||||
const core = createCoreScope();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
## Examples
|
||||
|
||||
`examples/` holds runnable **domain-slice scenarios**: each `examples/<name>.example.ts` is a vitest test that exercises one subset of domains end-to-end, so a single file teaches a single capability. Each example builds its **own** container (the `createServices` flat harness for Core-scope slices, or `bootstrap` + child scopes for tree-spanning slices), runs its slice's services for real, and stubs the collaborators outside the slice — so examples never need the full engine, and different examples stub different subsets. Tree-spanning examples redirect `KIMI_CODE_HOME` to a single `.vitest-results/kimi-code-{timestamp}/` per run (set once in `examples/_globalSetup.ts` and shared across every file in the invocation) and seed a file-backed `IAtomicDocumentStorage`, so persisted state is written to disk for inspection.
|
||||
`examples/` holds runnable **domain-slice scenarios**: each `examples/<name>.example.ts` is a vitest test that exercises one subset of domains end-to-end, so a single file teaches a single capability. Each example builds its **own** container (the `createServices` flat harness for App-scope slices, or `bootstrap` + child scopes for tree-spanning slices), runs its slice's services for real, and stubs the collaborators outside the slice — so examples never need the full engine, and different examples stub different subsets. Tree-spanning examples redirect `KIMI_CODE_HOME` to a single `.vitest-results/kimi-code-{timestamp}/` per run (set once in `examples/_globalSetup.ts` and shared across every file in the invocation) and seed a file-backed `IAtomicDocumentStorage`, so persisted state is written to disk for inspection.
|
||||
|
||||
Run one from the repo root with `pnpm dev:core-example <name>` (a filename filter; omit `<name>` to run them all). Examples use their own vitest project (`agent-core-v2-examples`, via `vitest.examples.config.ts`), so they are separate from the real `test/` suite and their console output is shown per scenario. Add a new example by adding an `examples/<name>.example.ts` that imports only its slice's domains.
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ Per-domain references live in `docs/`.
|
|||
|
||||
- [`docs/di.md`](docs/di.md) — Read **before adding any business capability**: a scenario-driven walkthrough of the DI × Scope black box, from "add a global service" through dependency injection, scope selection, disposal, delayed/eager instantiation, `invokeFunction`, `createInstance`, child scopes, and cycles — introducing each concept only as the scenario needs it.
|
||||
- [`docs/service-design.md`](docs/service-design.md) — Read **before designing a new Service**: first-principles rules for choosing a scope, splitting a domain Multi-Scope, picking a calling style (direct call vs event vs hook), and directing dependencies — the design companion to `docs/di.md`.
|
||||
- [`docs/flag.md`](docs/flag.md) — Read **before gating behavior behind a feature flag**: declaring a flag in its owning domain and registering it at import time via `registerFlagDefinition`, checking `IFlagService.enabled(id)`, wiring the `[experimental]` config section, or deciding whether a flag is Core-scope vs. per-session.
|
||||
- [`docs/flag.md`](docs/flag.md) — Read **before gating behavior behind a feature flag**: declaring a flag in its owning domain and registering it at import time via `registerFlagDefinition`, checking `IFlagService.enabled(id)`, wiring the `[experimental]` config section, or deciding whether a flag is App-scope vs. per-session.
|
||||
- [`docs/errors.md`](docs/errors.md) — Read **before raising errors from a domain**: defining a co-located `XxxError`, registering a code in `ErrorCodes`/`ERROR_INFO`, translating external errors (provider/HTTP, fs, MCP) at the boundary, or (de)serializing errors across RPC/SDK with `toErrorPayload`/`fromErrorPayload`.
|
||||
- [`docs/di-testing.md`](docs/di-testing.md) — Read **before writing or touching any DI/Scope test**: picking the right harness (`InstantiationService` vs `TestInstantiationService` vs `createScopedTestHost`), declaring deps with `@IService`, stubbing collaborators, and teardown via `DisposableStore`.
|
||||
- [`docs/di-scope-domains.puml`](docs/di-scope-domains.puml) — DI Scope × Domain dependency map (node color = `LifecycleScope`; solid edges = constructor DI injection, dashed edges = `wireRecord` / event-driven). **When adding a Service or changing the dependency relationships between Services, update this puml and regenerate `docs/di-scope-domains.svg`**.
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ interface PermissionPolicyEntry {
|
|||
factory: (accessor: ServicesAccessor) => PermissionPolicy;
|
||||
}
|
||||
|
||||
// Core scope —— 收集所有 domain 的注册
|
||||
// App scope —— 收集所有 domain 的注册
|
||||
interface IPermissionPolicyRegistry {
|
||||
register(entry: PermissionPolicyEntry): IDisposable;
|
||||
list(): readonly PermissionPolicyEntry[];
|
||||
|
|
@ -192,7 +192,7 @@ this.policies = registry.list()
|
|||
要点:
|
||||
|
||||
- `modes`/`agentTypes` 是**声明**,把现在 `YoloModeApprove` 里的 `if (mode !== 'yolo') return` 提到元数据。
|
||||
- `factory` 而非 `instance`:节点可能依赖 agent-scoped 服务(mode、rules),需在 Agent scope 实例化——对称 `IToolDefinitionRegistry`(Core) 存 factory、`IToolService`(Agent) 实例化工具。
|
||||
- `factory` 而非 `instance`:节点可能依赖 agent-scoped 服务(mode、rules),需在 Agent scope 实例化——对称 `IToolDefinitionRegistry`(App) 存 factory、`IToolService`(Agent) 实例化工具。
|
||||
- **不同 (agent, mode) 产出形状不同的链**:yolo 下 ask/fallback 阶段被物理过滤掉。
|
||||
|
||||
### 5.3 两条贡献路径
|
||||
|
|
@ -212,11 +212,11 @@ this.policies = registry.list()
|
|||
// src/plan/planService.ts
|
||||
constructor(@IPermissionPolicyRegistry registry: IPermissionPolicyRegistry) {
|
||||
registry.register({ name: 'plan-mode-guard-deny', phase: 'guard',
|
||||
factory: a => new PlanModeGuardDenyPolicy(a.get(IPlanService)) });
|
||||
factory: a => new PlanModeGuardDenyPolicy(a.get(IAgentPlanService)) });
|
||||
registry.register({ name: 'plan-mode-tool-approve', phase: 'mode',
|
||||
factory: a => new PlanModeToolApprovePolicy(a.get(IPlanService)) });
|
||||
factory: a => new PlanModeToolApprovePolicy(a.get(IAgentPlanService)) });
|
||||
registry.register({ name: 'exit-plan-mode-review-ask', phase: 'user-ask',
|
||||
factory: a => new ExitPlanModeReviewAskPolicy(a.get(IPlanService), a.get(IPermissionModeService)) });
|
||||
factory: a => new ExitPlanModeReviewAskPolicy(a.get(IAgentPlanService), a.get(IAgentPermissionModeService)) });
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ skinparam ranksep 55
|
|||
|
||||
legend right
|
||||
<b>Scope = node color</b>
|
||||
<back:#D6EAF8> </back> Core (process-wide)
|
||||
<back:#D6EAF8> </back> App (process-wide)
|
||||
<back:#D5F5E3> </back> Session (per session)
|
||||
<back:#FDEBD0> </back> Agent (per agent)
|
||||
== Edges ==
|
||||
|
|
@ -18,27 +18,27 @@ legend right
|
|||
`_base` / seed / options deps are omitted.
|
||||
endlegend
|
||||
|
||||
package "Core scope (process-wide)" #EAF3FB {
|
||||
rectangle "<b>bootstrap</b>\n<size:9><i>Core</i></size>\n IBootstrapService" as bootstrap #D6EAF8
|
||||
rectangle "<b>log</b>\n<size:9><i>Core</i></size>\n ILogService\n ILogWriterService" as log #D6EAF8
|
||||
rectangle "<b>telemetry</b>\n<size:9><i>Core</i></size>\n ITelemetryService" as telemetry #D6EAF8
|
||||
rectangle "<b>event</b>\n<size:9><i>Core</i></size>\n IEventService" as event #D6EAF8
|
||||
rectangle "<b>storage</b>\n<size:9><i>Core</i></size>\n IStorageService\n IAppendLogStore\n IAtomicDocumentStore\n IAtomicTomlDocumentStore" as storage #D6EAF8
|
||||
rectangle "<b>filestore</b>\n<size:9><i>Core</i></size>\n IFileStore" as filestore #D6EAF8
|
||||
rectangle "<b>gateway</b>\n<size:9><i>Core</i></size>\n IRestGateway\n IWSGateway\n IWSBroadcastService" as gateway #D6EAF8
|
||||
rectangle "<b>session-lifecycle</b>\n<size:9><i>Core</i></size>\n ISessionLifecycleService" as session_lifecycle #D6EAF8
|
||||
rectangle "<b>session-index</b>\n<size:9><i>Core</i></size>\n ISessionIndex" as sessionIndex #D6EAF8
|
||||
rectangle "<b>hostFs</b>\n<size:9><i>Core</i></size>\n IHostFileSystem" as hostFs #D6EAF8
|
||||
rectangle "<b>workspaceRegistry</b>\n<size:9><i>Core</i></size>\n IWorkspaceRegistry" as workspaceRegistry #D6EAF8
|
||||
rectangle "<b>hostFolderBrowser</b>\n<size:9><i>Core</i></size>\n IHostFolderBrowser" as hostFolderBrowser #D6EAF8
|
||||
rectangle "<b>kaos</b>\n<size:9><i>Core</i></size>\n IKaosFactory" as kaos_core #D6EAF8
|
||||
rectangle "<b>auth</b>\n<size:9><i>Core</i></size>\n IOAuthService\n IAuthSummaryService" as auth #D6EAF8
|
||||
rectangle "<b>provider</b>\n<size:9><i>Core</i></size>\n IProviderService" as provider #D6EAF8
|
||||
rectangle "<b>flag</b>\n<size:9><i>Core</i></size>\n IFlagService\n IFlagRegistry" as flag #D6EAF8
|
||||
rectangle "<b>config</b>\n<size:9><i>Core</i></size>\n IConfigRegistry\n IConfigService" as config #D6EAF8
|
||||
rectangle "<b>chatProvider</b>\n<size:9><i>Core</i></size>\n IChatProviderFactory" as chatProvider #D6EAF8
|
||||
rectangle "<b>model</b>\n<size:9><i>Core</i></size>\n IModelService" as model #D6EAF8
|
||||
rectangle "<b>modelCatalog</b>\n<size:9><i>Core</i></size>\n IModelCatalogService" as modelCatalog #D6EAF8
|
||||
package "App scope (process-wide)" #EAF3FB {
|
||||
rectangle "<b>bootstrap</b>\n<size:9><i>App</i></size>\n IBootstrapService" as bootstrap #D6EAF8
|
||||
rectangle "<b>log</b>\n<size:9><i>App</i></size>\n ILogService\n ILogWriterService" as log #D6EAF8
|
||||
rectangle "<b>telemetry</b>\n<size:9><i>App</i></size>\n ITelemetryService" as telemetry #D6EAF8
|
||||
rectangle "<b>event</b>\n<size:9><i>App</i></size>\n IEventService" as event #D6EAF8
|
||||
rectangle "<b>storage</b>\n<size:9><i>App</i></size>\n IStorageService\n IAppendLogStore\n IAtomicDocumentStore\n IAtomicTomlDocumentStore" as storage #D6EAF8
|
||||
rectangle "<b>filestore</b>\n<size:9><i>App</i></size>\n IFileStore" as filestore #D6EAF8
|
||||
rectangle "<b>gateway</b>\n<size:9><i>App</i></size>\n IRestGateway\n IWSGateway\n IWSBroadcastService" as gateway #D6EAF8
|
||||
rectangle "<b>session-lifecycle</b>\n<size:9><i>App</i></size>\n ISessionLifecycleService" as session_lifecycle #D6EAF8
|
||||
rectangle "<b>session-index</b>\n<size:9><i>App</i></size>\n ISessionIndex" as sessionIndex #D6EAF8
|
||||
rectangle "<b>hostFs</b>\n<size:9><i>App</i></size>\n IHostFileSystem" as hostFs #D6EAF8
|
||||
rectangle "<b>workspaceRegistry</b>\n<size:9><i>App</i></size>\n IWorkspaceRegistry" as workspaceRegistry #D6EAF8
|
||||
rectangle "<b>hostFolderBrowser</b>\n<size:9><i>App</i></size>\n IHostFolderBrowser" as hostFolderBrowser #D6EAF8
|
||||
rectangle "<b>kaos</b>\n<size:9><i>App</i></size>\n IKaosFactory" as kaos_core #D6EAF8
|
||||
rectangle "<b>auth</b>\n<size:9><i>App</i></size>\n IOAuthService\n IAuthSummaryService" as auth #D6EAF8
|
||||
rectangle "<b>provider</b>\n<size:9><i>App</i></size>\n IProviderService" as provider #D6EAF8
|
||||
rectangle "<b>flag</b>\n<size:9><i>App</i></size>\n IFlagService\n IFlagRegistry" as flag #D6EAF8
|
||||
rectangle "<b>config</b>\n<size:9><i>App</i></size>\n IConfigRegistry\n IConfigService" as config #D6EAF8
|
||||
rectangle "<b>chatProvider</b>\n<size:9><i>App</i></size>\n IChatProviderFactory" as chatProvider #D6EAF8
|
||||
rectangle "<b>model</b>\n<size:9><i>App</i></size>\n IModelService" as model #D6EAF8
|
||||
rectangle "<b>modelCatalog</b>\n<size:9><i>App</i></size>\n IModelCatalogService" as modelCatalog #D6EAF8
|
||||
}
|
||||
|
||||
package "Session scope (per session)" #EAFAF1 {
|
||||
|
|
@ -61,45 +61,45 @@ package "Session scope (per session)" #EAFAF1 {
|
|||
}
|
||||
|
||||
package "Agent scope (per agent)" #FDF5E6 {
|
||||
rectangle "<b>wireRecord</b>\n<size:9><i>Agent</i></size>\n IWireRecord (event hub)" as wireRecord #FDEBD0
|
||||
rectangle "<b>eventSink</b>\n<size:9><i>Agent</i></size>\n IEventSink" as eventSink #FDEBD0
|
||||
rectangle "<b>blobStore</b>\n<size:9><i>Agent</i></size>\n IBlobStoreService" as blobStore #FDEBD0
|
||||
rectangle "<b>contextMemory</b>\n<size:9><i>Agent</i></size>\n IContextMemory" as contextMemory #FDEBD0
|
||||
rectangle "<b>contextProjector</b>\n<size:9><i>Agent</i></size>\n IContextProjector" as contextProjector #FDEBD0
|
||||
rectangle "<b>contextInjector</b>\n<size:9><i>Agent</i></size>\n IContextInjector" as contextInjector #FDEBD0
|
||||
rectangle "<b>contextSize</b>\n<size:9><i>Agent</i></size>\n IContextSizeService" as contextSize #FDEBD0
|
||||
rectangle "<b>systemReminder</b>\n<size:9><i>Agent</i></size>\n ISystemReminderService" as systemReminder #FDEBD0
|
||||
rectangle "<b>replayBuilder</b>\n<size:9><i>Agent</i></size>\n IReplayBuilderService" as replayBuilder #FDEBD0
|
||||
rectangle "<b>profile</b>\n<size:9><i>Agent</i></size>\n IProfileService" as profile #FDEBD0
|
||||
rectangle "<b>prompt</b>\n<size:9><i>Agent</i></size>\n IPromptService" as prompt #FDEBD0
|
||||
rectangle "<b>turn</b>\n<size:9><i>Agent</i></size>\n ITurnService" as turn #FDEBD0
|
||||
rectangle "<b>loop</b>\n<size:9><i>Agent</i></size>\n ILoopService" as loop #FDEBD0
|
||||
rectangle "<b>llmRequester</b>\n<size:9><i>Agent</i></size>\n ILLMRequester" as llmRequester #FDEBD0
|
||||
rectangle "<b>llmRequestLog</b>\n<size:9><i>Agent</i></size>\n ILLMRequestLogService" as llmRequestLog #FDEBD0
|
||||
rectangle "<b>toolRegistry</b>\n<size:9><i>Agent</i></size>\n IToolRegistry" as toolRegistry #FDEBD0
|
||||
rectangle "<b>toolExecutor</b>\n<size:9><i>Agent</i></size>\n IToolExecutor" as toolExecutor #FDEBD0
|
||||
rectangle "<b>toolStore</b>\n<size:9><i>Agent</i></size>\n IToolStoreService" as toolStore #FDEBD0
|
||||
rectangle "<b>toolDedup</b>\n<size:9><i>Agent</i></size>\n IToolDedupe" as toolDedup #FDEBD0
|
||||
rectangle "<b>permissionGate</b>\n<size:9><i>Agent</i></size>\n IPermissionGate" as permissionGate #FDEBD0
|
||||
rectangle "<b>permissionMode</b>\n<size:9><i>Agent</i></size>\n IPermissionModeService" as permissionMode #FDEBD0
|
||||
rectangle "<b>permissionPolicy</b>\n<size:9><i>Agent</i></size>\n IPermissionPolicyService" as permissionPolicy #FDEBD0
|
||||
rectangle "<b>permissionRules</b>\n<size:9><i>Agent</i></size>\n IPermissionRulesService" as permissionRules #FDEBD0
|
||||
rectangle "<b>plan</b>\n<size:9><i>Agent</i></size>\n IPlanService" as plan #FDEBD0
|
||||
rectangle "<b>goal</b>\n<size:9><i>Agent</i></size>\n IGoalService" as goal #FDEBD0
|
||||
rectangle "<b>wireRecord</b>\n<size:9><i>Agent</i></size>\n IAgentWireRecordService (event hub)" as wireRecord #FDEBD0
|
||||
rectangle "<b>eventSink</b>\n<size:9><i>Agent</i></size>\n IAgentEventSinkService" as eventSink #FDEBD0
|
||||
rectangle "<b>blobStore</b>\n<size:9><i>Agent</i></size>\n IAgentBlobStoreService" as blobStore #FDEBD0
|
||||
rectangle "<b>contextMemory</b>\n<size:9><i>Agent</i></size>\n IAgentContextMemoryService" as contextMemory #FDEBD0
|
||||
rectangle "<b>contextProjector</b>\n<size:9><i>Agent</i></size>\n IAgentContextProjectorService" as contextProjector #FDEBD0
|
||||
rectangle "<b>contextInjector</b>\n<size:9><i>Agent</i></size>\n IAgentContextInjectorService" as contextInjector #FDEBD0
|
||||
rectangle "<b>contextSize</b>\n<size:9><i>Agent</i></size>\n IAgentContextSizeService" as contextSize #FDEBD0
|
||||
rectangle "<b>systemReminder</b>\n<size:9><i>Agent</i></size>\n IAgentSystemReminderService" as systemReminder #FDEBD0
|
||||
rectangle "<b>replayBuilder</b>\n<size:9><i>Agent</i></size>\n IAgentReplayBuilderService" as replayBuilder #FDEBD0
|
||||
rectangle "<b>profile</b>\n<size:9><i>Agent</i></size>\n IAgentProfileService" as profile #FDEBD0
|
||||
rectangle "<b>prompt</b>\n<size:9><i>Agent</i></size>\n IAgentPromptService" as prompt #FDEBD0
|
||||
rectangle "<b>turn</b>\n<size:9><i>Agent</i></size>\n IAgentTurnService" as turn #FDEBD0
|
||||
rectangle "<b>loop</b>\n<size:9><i>Agent</i></size>\n IAgentLoopService" as loop #FDEBD0
|
||||
rectangle "<b>llmRequester</b>\n<size:9><i>Agent</i></size>\n IAgentLLMRequesterService" as llmRequester #FDEBD0
|
||||
rectangle "<b>llmRequestLog</b>\n<size:9><i>Agent</i></size>\n IAgentLLMRequestLogService" as llmRequestLog #FDEBD0
|
||||
rectangle "<b>toolRegistry</b>\n<size:9><i>Agent</i></size>\n IAgentToolRegistryService" as toolRegistry #FDEBD0
|
||||
rectangle "<b>toolExecutor</b>\n<size:9><i>Agent</i></size>\n IAgentToolExecutorService" as toolExecutor #FDEBD0
|
||||
rectangle "<b>toolStore</b>\n<size:9><i>Agent</i></size>\n IAgentToolStoreService" as toolStore #FDEBD0
|
||||
rectangle "<b>toolDedup</b>\n<size:9><i>Agent</i></size>\n IAgentToolDedupeService" as toolDedup #FDEBD0
|
||||
rectangle "<b>permissionGate</b>\n<size:9><i>Agent</i></size>\n IAgentPermissionGate" as permissionGate #FDEBD0
|
||||
rectangle "<b>permissionMode</b>\n<size:9><i>Agent</i></size>\n IAgentPermissionModeService" as permissionMode #FDEBD0
|
||||
rectangle "<b>permissionPolicy</b>\n<size:9><i>Agent</i></size>\n IAgentPermissionPolicyService" as permissionPolicy #FDEBD0
|
||||
rectangle "<b>permissionRules</b>\n<size:9><i>Agent</i></size>\n IAgentPermissionRulesService" as permissionRules #FDEBD0
|
||||
rectangle "<b>plan</b>\n<size:9><i>Agent</i></size>\n IAgentPlanService" as plan #FDEBD0
|
||||
rectangle "<b>goal</b>\n<size:9><i>Agent</i></size>\n IAgentGoalService" as goal #FDEBD0
|
||||
rectangle "<b>skill</b>\n<size:9><i>Agent</i></size>\n IAgentSkillService" as skill #FDEBD0
|
||||
rectangle "<b>userTool</b>\n<size:9><i>Agent</i></size>\n IUserToolService" as userTool #FDEBD0
|
||||
rectangle "<b>background</b>\n<size:9><i>Agent</i></size>\n IBackgroundService" as background #FDEBD0
|
||||
rectangle "<b>cron</b>\n<size:9><i>Agent</i></size>\n ICronService" as cron #FDEBD0
|
||||
rectangle "<b>swarm</b>\n<size:9><i>Agent</i></size>\n ISwarmService" as swarm #FDEBD0
|
||||
rectangle "<b>mcp</b>\n<size:9><i>Agent</i></size>\n IMcpService" as mcp #FDEBD0
|
||||
rectangle "<b>fullCompaction</b>\n<size:9><i>Agent</i></size>\n IFullCompaction" as fullCompaction #FDEBD0
|
||||
rectangle "<b>microCompaction</b>\n<size:9><i>Agent</i></size>\n IMicroCompactionService" as microCompaction #FDEBD0
|
||||
rectangle "<b>externalHooks</b>\n<size:9><i>Agent</i></size>\n IExternalHooksService" as externalHooks #FDEBD0
|
||||
rectangle "<b>todoList</b>\n<size:9><i>Agent</i></size>\n ITodoListService" as todoList #FDEBD0
|
||||
rectangle "<b>usage</b>\n<size:9><i>Agent</i></size>\n IUsageService" as usage #FDEBD0
|
||||
rectangle "<b>userTool</b>\n<size:9><i>Agent</i></size>\n IAgentUserToolService" as userTool #FDEBD0
|
||||
rectangle "<b>background</b>\n<size:9><i>Agent</i></size>\n IAgentBackgroundService" as background #FDEBD0
|
||||
rectangle "<b>cron</b>\n<size:9><i>Agent</i></size>\n IAgentCronService" as cron #FDEBD0
|
||||
rectangle "<b>swarm</b>\n<size:9><i>Agent</i></size>\n IAgentSwarmService" as swarm #FDEBD0
|
||||
rectangle "<b>mcp</b>\n<size:9><i>Agent</i></size>\n IAgentMcpService" as mcp #FDEBD0
|
||||
rectangle "<b>fullCompaction</b>\n<size:9><i>Agent</i></size>\n IAgentFullCompactionService" as fullCompaction #FDEBD0
|
||||
rectangle "<b>microCompaction</b>\n<size:9><i>Agent</i></size>\n IAgentMicroCompactionService" as microCompaction #FDEBD0
|
||||
rectangle "<b>externalHooks</b>\n<size:9><i>Agent</i></size>\n IAgentExternalHooksService" as externalHooks #FDEBD0
|
||||
rectangle "<b>todoList</b>\n<size:9><i>Agent</i></size>\n IAgentTodoListService" as todoList #FDEBD0
|
||||
rectangle "<b>usage</b>\n<size:9><i>Agent</i></size>\n IAgentUsageService" as usage #FDEBD0
|
||||
rectangle "<b>rpc</b>\n<size:9><i>Agent</i></size>\n IAgentRPCService" as rpc #FDEBD0
|
||||
rectangle "<b>fileTools</b>\n<size:9><i>Agent</i></size>\n IFileToolsService" as fileTools #FDEBD0
|
||||
rectangle "<b>shellTools</b>\n<size:9><i>Agent</i></size>\n IShellToolsService" as shellTools #FDEBD0
|
||||
rectangle "<b>fileTools</b>\n<size:9><i>Agent</i></size>\n IAgentFileToolsService" as fileTools #FDEBD0
|
||||
rectangle "<b>shellTools</b>\n<size:9><i>Agent</i></size>\n IAgentShellToolsService" as shellTools #FDEBD0
|
||||
}
|
||||
|
||||
' ---- DI injection (solid) ----
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 230 KiB |
|
|
@ -64,7 +64,7 @@ export class Greeter implements IGreeter {
|
|||
```ts
|
||||
// greet/greetService.ts(文件顶层,import 时执行)
|
||||
registerScopedService(
|
||||
LifecycleScope.Core, // 活多久:进程级
|
||||
LifecycleScope.App, // 活多久:进程级
|
||||
IGreeter, // 身份
|
||||
Greeter, // 实现
|
||||
InstantiationType.Eager, // 创建时机:立刻
|
||||
|
|
@ -143,7 +143,7 @@ const session = accessor.get(ISessionService); // 类型是 ISessionService
|
|||
|
||||
```ts
|
||||
export enum LifecycleScope {
|
||||
Core = 0, // 进程级,全局一份
|
||||
App = 0, // 进程级,全局一份
|
||||
Session = 1, // 一次会话
|
||||
Agent = 2, // 一个 agent
|
||||
}
|
||||
|
|
@ -155,14 +155,14 @@ export enum LifecycleScope {
|
|||
registerScopedService(LifecycleScope.Session, ISessionService, SessionService, InstantiationType.Delayed, 'session');
|
||||
```
|
||||
|
||||
「单例」的粒度是**每个 scope 一份**:Core 的 `ILogService` 全局只有一份;每个 Session scope 各有自己的 `ISessionService`。
|
||||
「单例」的粒度是**每个 scope 一份**:App 的 `ILogService` 全局只有一份;每个 Session scope 各有自己的 `ISessionService`。
|
||||
|
||||
### 3.2 子 scope 看得见父 scope,反之不行
|
||||
|
||||
Scope 是一棵树,`kind` 必须沿父子方向**严格递增**:
|
||||
|
||||
```
|
||||
Core (0)
|
||||
App (0)
|
||||
└── Session (1)
|
||||
└── Agent (2)
|
||||
```
|
||||
|
|
@ -171,8 +171,8 @@ Core (0)
|
|||
|
||||
> **短寿命的服务可以注入长寿命的服务,反过来不行。**
|
||||
|
||||
- ✅ Agent 服务注入 Session / Core 服务(往上找,找得到)。
|
||||
- ❌ Core 服务注入 Session 服务(Core 创建时 Session 还不存在,且父不会往下找)。
|
||||
- ✅ Agent 服务注入 Session / App 服务(往上找,找得到)。
|
||||
- ❌ App 服务注入 Session 服务(App 创建时 Session 还不存在,且父不会往下找)。
|
||||
|
||||
这条规则由树的结构强制保证,不靠纪律维持。
|
||||
|
||||
|
|
@ -212,10 +212,10 @@ export class WSBroadcastService extends Disposable implements IWSBroadcastServic
|
|||
|
||||
```ts
|
||||
// Eager:scope 创建时立刻 new
|
||||
registerScopedService(LifecycleScope.Core, ILogService, LogService, InstantiationType.Eager, 'log');
|
||||
registerScopedService(LifecycleScope.App, ILogService, LogService, InstantiationType.Eager, 'log');
|
||||
|
||||
// Delayed:第一次被 get 时才 new
|
||||
registerScopedService(LifecycleScope.Core, IScopeRegistry, ScopeRegistry, InstantiationType.Delayed, 'gateway');
|
||||
registerScopedService(LifecycleScope.App, IScopeRegistry, ScopeRegistry, InstantiationType.Delayed, 'gateway');
|
||||
```
|
||||
|
||||
Delayed 服务返回的是一个 **Proxy**:在首次访问任意属性时才真正构造。即便还没构造好,别人提前订阅它的 `onDid…` / `onWill…` 事件也不会丢——容器会先记下监听器,实例真正出来后再回放订阅。
|
||||
|
|
@ -300,7 +300,7 @@ export class ScopeRegistry implements IScopeRegistry {
|
|||
关键点:
|
||||
|
||||
- `getScopedServiceDescriptors(scope)` 能拿回注册在某一层的所有描述符,装进一个 `ServiceCollection`。
|
||||
- `instantiation.createChild(collection)` 造一个子容器,它的父指针指向当前容器——于是子容器能向上解析到 Core 的服务(场景 3 的可见性规则)。
|
||||
- `instantiation.createChild(collection)` 造一个子容器,它的父指针指向当前容器——于是子容器能向上解析到 App 的服务(场景 3 的可见性规则)。
|
||||
- 给外部暴露时,用 `invokeFunction` 把子容器包成 `ServicesAccessor`(场景 6)。
|
||||
|
||||
> 更高层通常直接用 [`Scope.createChild(kind, id)`](../src/_base/di/scope.ts)(它帮你做了「筛描述符 + 建子容器」);只有需要手动控制 `ServiceCollection` 时才像上面这样写。
|
||||
|
|
@ -317,7 +317,7 @@ A 创建中要 B,B 创建中又要 A——容器会抛 `CyclicDependencyError`
|
|||
|
||||
### 9.2 为什么不允许
|
||||
|
||||
- scope 分层让正常依赖天然是 DAG(Agent → Session → Core 向上找),一个环几乎总是设计味道。
|
||||
- scope 分层让正常依赖天然是 DAG(Agent → Session → App 向上找),一个环几乎总是设计味道。
|
||||
- 靠「让环刚好能跑」会把构造顺序变成隐式约定,难调试、难排错。
|
||||
|
||||
所以 v2 的立场是:**依赖图必须是无环的。**
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
# flag
|
||||
|
||||
> Experimental feature-flag gating for agent-core-v2 — a Core-scope `IFlagService` resolver plus a writable `IFlagRegistry` catalog that domains contribute their flags to, backed by the `[experimental]` config section.
|
||||
> Experimental feature-flag gating for agent-core-v2 — a App-scope `IFlagService` resolver plus a writable `IFlagRegistry` catalog that domains contribute their flags to, backed by the `[experimental]` config section.
|
||||
|
||||
Gates not-yet-public features behind `IFlagService.enabled(id)`, per the repository hard rule that unreleased behavior must be flag-gated. Ported from `packages/agent-core/src/flags/**`; v1 was a process-global `FlagResolver` singleton over a central `FLAG_DEFINITIONS` array, v2 is a scoped DI service whose flag definitions are registered **decentrally** by each owning domain — there is no central catalog to edit.
|
||||
|
||||
## 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; Core scope.
|
||||
- `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 Core scope.
|
||||
- `src/flag/flagService.ts` — `FlagService` impl + `MASTER_ENV` (`KIMI_CODE_EXPERIMENTAL_FLAG`) + `EXPERIMENTAL_SECTION` (`experimental`); reads definitions from `IFlagRegistry`; self-registers at App scope.
|
||||
- `src/flag/index.ts` — barrel; re-exported by `src/index.ts` at the L3 block.
|
||||
- `src/<domain>/flag.ts` — each domain that owns a flag declares it here and calls `registerFlagDefinition` at the module top level (e.g. `src/microCompaction/flag.ts`). The directory already names the domain, so the file is just `flag.ts`.
|
||||
|
||||
## Public surface
|
||||
|
||||
- `IFlagService` (DI token, Core scope): `enabled(id)`, `explain(id)`, `snapshot()`, `enabledIds()`, `explainAll()`, `setConfigOverrides(overrides)`, `registry`.
|
||||
- `IFlagRegistry` (DI token, Core scope): `register(definition)`, `get(id)`, `list()` — writable catalog. `register` is the **runtime** path (tests, dynamic registration); `IFlagService.registry` exposes the same instance for hosts/UI to enumerate flags without resolving them.
|
||||
- `IFlagService` (DI token, App scope): `enabled(id)`, `explain(id)`, `snapshot()`, `enabledIds()`, `explainAll()`, `setConfigOverrides(overrides)`, `registry`.
|
||||
- `IFlagRegistry` (DI token, App scope): `register(definition)`, `get(id)`, `list()` — writable catalog. `register` is the **runtime** path (tests, dynamic registration); `IFlagService.registry` exposes the same instance for hosts/UI to enumerate flags without resolving them.
|
||||
- `registerFlagDefinition(definition)` — the **import-time** path. Domains call this from their `flag.ts` top level; contributions are queued and drained by `FlagRegistryService` when it is instantiated.
|
||||
- `FlagService` / `FlagRegistryService`: exported for tests and hosts that construct them directly.
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ export * from './flag';
|
|||
|
||||
## Consume a flag
|
||||
|
||||
Inject `IFlagService` and gate on it. It is resolvable from any scope (Core ancestor):
|
||||
Inject `IFlagService` and gate on it. It is resolvable from any scope (App ancestor):
|
||||
|
||||
```ts
|
||||
constructor(@IFlagService private readonly flags: IFlagService) {}
|
||||
|
|
@ -99,7 +99,7 @@ Current consumer: `microCompaction` (Agent scope) gates `micro_compaction`.
|
|||
|
||||
- Domain `flag` is registered at **L3** (`scripts/check-domain-layers.mjs` → `['flag', 3]`). 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.
|
||||
- Scope: `IFlagRegistry` and `IFlagService` are both `Core`. 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.
|
||||
- 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 (`test/flag/flag.test.ts`).
|
||||
|
||||
## References
|
||||
|
|
|
|||
|
|
@ -37,11 +37,11 @@ Every principle below derives from two root questions:
|
|||
|
||||
**First principle: Scope = the identity + lifetime of the owned state.**
|
||||
|
||||
`Core` / `Session` / `Agent` are three tiers of identity + lifetime:
|
||||
`App` / `Session` / `Agent` are three tiers of identity + lifetime:
|
||||
|
||||
| Scope | State identity (keyed by) | Lifetime |
|
||||
|---|---|---|
|
||||
| `Core` | none (single global instance) | the process |
|
||||
| `App` | none (single global instance) | the process |
|
||||
| `Session` | `sessionId` | one session |
|
||||
| `Agent` | `agentId` | one agent |
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ Every principle below derives from two root questions:
|
|||
|
||||
**Q2. What is the identity of that state?**
|
||||
|
||||
- one global instance → **`Core`**
|
||||
- one global instance → **`App`**
|
||||
- one per session → **`Session`**
|
||||
- one per agent → **`Agent`**
|
||||
- a mix (a global registry *and* per-instance state) → **do not put it in one Service;
|
||||
|
|
@ -63,8 +63,8 @@ Every principle below derives from two root questions:
|
|||
**Q3 (stateless). What is the shortest-lived dependency it must inject?**
|
||||
|
||||
A stateless Service is pulled *down* by its shortest-lived dependency: if it injects an
|
||||
`Agent`-scoped Service, it cannot be `Core`. Among the scopes that still satisfy every
|
||||
dependency, **default to the longest-lived one** (usually `Core`) to maximize reuse and
|
||||
`Agent`-scoped Service, it cannot be `App`. Among the scopes that still satisfy every
|
||||
dependency, **default to the longest-lived one** (usually `App`) to maximize reuse and
|
||||
singleton sharing. Push it down only when:
|
||||
|
||||
1. it must inject a shorter-lived Service (enforced by the container); or
|
||||
|
|
@ -73,10 +73,10 @@ singleton sharing. Push it down only when:
|
|||
|
||||
### The core anti-pattern (a litmus test)
|
||||
|
||||
> **Do not store per-session state in a `Map<sessionId, …>` inside a `Core` Service.**
|
||||
> **Do not store per-session state in a `Map<sessionId, …>` inside a `App` Service.**
|
||||
|
||||
This is the tell-tale sign of "this should have been `Session`-scoped but was lazily parked
|
||||
at `Core`". Consequences:
|
||||
at `App`". Consequences:
|
||||
|
||||
- nobody cleans the entry up when the session ends → **leak**;
|
||||
- every consumer threads `sessionId` around → **loss of type safety**;
|
||||
|
|
@ -107,36 +107,36 @@ job well.
|
|||
|
||||
| Tier | Role | Naming tends to |
|
||||
|---|---|---|
|
||||
| `Core` | **global registry / catalog / factory** — knows "all of them" and how to create one | `XxxStore` / `XxxRegistry` / `XxxCatalog` |
|
||||
| `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` |
|
||||
|
||||
This pattern recurs throughout the codebase and confirms the rule:
|
||||
|
||||
- **`records`** — `ISessionIndex` (`Core`, read model of all persisted sessions) +
|
||||
`ISessionMetadata` (`Session`, this session's metadata) + `IWireRecord` (`Agent`, this
|
||||
- **`records`** — `ISessionIndex` (`App`, read model of all persisted sessions) +
|
||||
`ISessionMetadata` (`Session`, this session's metadata) + `IAgentWireRecordService` (`Agent`, this
|
||||
agent's record stream).
|
||||
- **`config`** — `IConfigRegistry` / `IConfigService` (`Core`, global config).
|
||||
- **`chatProvider` / `model` / `modelRuntime`** — `IChatProviderFactory` (`Core`,
|
||||
protocol adapters keyed by provider type), `IModelService` (`Core`, model-alias
|
||||
- **`config`** — `IConfigRegistry` / `IConfigService` (`App`, global config).
|
||||
- **`chatProvider` / `model` / `modelRuntime`** — `IChatProviderFactory` (`App`,
|
||||
protocol adapters keyed by provider type), `IModelService` (`App`, model-alias
|
||||
configuration), and `IModelResolver` (`Session`, resolves the active model into a
|
||||
runtime provider config plus request authorization). Provider connection
|
||||
configuration lives in the sibling `provider` domain (`IProviderService`, `Core`).
|
||||
Generation itself is driven by `ILLMRequester` (`Agent`) in the `llmRequester`
|
||||
configuration lives in the sibling `provider` domain (`IProviderService`, `App`).
|
||||
Generation itself is driven by `IAgentLLMRequesterService` (`Agent`) in the `llmRequester`
|
||||
domain.
|
||||
- **`tool`** — `IToolDefinitionRegistry` (`Core`, tool-definition registry) + `IToolService`
|
||||
- **`tool`** — `IToolDefinitionRegistry` (`App`, tool-definition registry) + `IToolService`
|
||||
(`Agent`, this agent's execution).
|
||||
|
||||
### When to split and when not to
|
||||
|
||||
- **Split** when the domain genuinely has both a global view and per-instance state.
|
||||
- **Do not split** when the domain has state at only one lifetime (e.g. purely `Core` like
|
||||
- **Do not split** when the domain has state at only one lifetime (e.g. purely `App` like
|
||||
`log` / `telemetry`; purely `Agent` like `prompt`). **Do not pre-split for symmetry.**
|
||||
|
||||
### Dependency direction after the split
|
||||
|
||||
The `Core` Service usually plays the **factory**: it knows how to create or locate the
|
||||
The `App` Service usually plays the **factory**: it knows how to create or locate the
|
||||
per-instance one. Most consumers inject the **per-instance** Service, because it serves the
|
||||
current session/agent directly without threading an id. Inject the `Core` factory only when
|
||||
current session/agent directly without threading an id. Inject the `App` factory only when
|
||||
you genuinely need cross-instance management.
|
||||
|
||||
---
|
||||
|
|
@ -277,8 +277,8 @@ The complete checklist for a new `IXxxService`:
|
|||
## 7. Summary
|
||||
|
||||
- **Scope**: the **identity** of the state fixes the scope; do not fake per-instance state
|
||||
at `Core` with a `Map<id, …>`.
|
||||
- **Multi-Scope**: a domain with state at several lifetimes → split into "a `Core` registry
|
||||
at `App` with a `Map<id, …>`.
|
||||
- **Multi-Scope**: a domain with state at several lifetimes → split into "a `App` registry
|
||||
+ per-instance Services".
|
||||
- **Calling style**: need a result / I orchestrate → direct call; stating a fact / react if
|
||||
you care → event; ordered participation / may veto → hook.
|
||||
|
|
|
|||
|
|
@ -7,24 +7,24 @@
|
|||
* resolves **every** current section owner so its `registerSection` runs, then
|
||||
* reads the single `IConfigRegistry` / `IConfigService` they all populated:
|
||||
*
|
||||
* Core-scope owners (resolved from the production `bootstrap` Core scope):
|
||||
* App-scope owners (resolved from the production `bootstrap` App scope):
|
||||
* - `IModelService` → `models` (+ the `KIMI_MODEL_*` overlay)
|
||||
* - `IProviderService` → `providers`
|
||||
* - `IFlagService` → `experimental`
|
||||
*
|
||||
* Agent-scope owners (constructed here against the same registry):
|
||||
* - `IBackgroundService` → `background`
|
||||
* - `ICronService` → `cron`
|
||||
* - `IPermissionRulesService`→ `permission`
|
||||
* - `IProfileService` → `thinking`, `defaultThinking`
|
||||
* - `ILoopService` → `loopControl`
|
||||
* - `IExternalHooksService` → `hooks`
|
||||
* - `IAgentBackgroundService` → `background`
|
||||
* - `IAgentCronService` → `cron`
|
||||
* - `IAgentPermissionRulesService`→ `permission`
|
||||
* - `IAgentProfileService` → `thinking`, `defaultThinking`
|
||||
* - `IAgentLoopService` → `loopControl`
|
||||
* - `IAgentExternalHooksService` → `hooks`
|
||||
*
|
||||
* The Agent owners are constructed through `createServices` with their
|
||||
* non-config collaborators stubbed, mirroring how the slice tests isolate a
|
||||
* domain (see `feature-flags.example.ts`). Only the `registerSection` call and
|
||||
* the config reads are real — which is exactly what this example is about. The
|
||||
* Core owners are *not* re-constructed: they are resolved from the real Core
|
||||
* App owners are *not* re-constructed: they are resolved from the real App
|
||||
* scope, so no section is registered twice.
|
||||
*
|
||||
* Two scenarios are shown:
|
||||
|
|
@ -48,39 +48,39 @@ import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|||
import { DisposableStore, toDisposable } from '#/_base/di/lifecycle';
|
||||
import type { Scope } from '#/_base/di/scope';
|
||||
import { createServices, type TestInstantiationService } from '#/_base/di/test';
|
||||
import { BackgroundService, IBackgroundService } from '#/background';
|
||||
import { AgentBackgroundService, IAgentBackgroundService } from '#/background';
|
||||
import { bootstrap } from '#/bootstrap/bootstrap';
|
||||
import { type ConfigInspectValue, IConfigRegistry, IConfigService } from '#/config/config';
|
||||
import '#/config/index';
|
||||
import { IContextMemory } from '#/contextMemory';
|
||||
import { IContextProjector } from '#/contextProjector';
|
||||
import { IContextSizeService } from '#/contextSize';
|
||||
import { CronService } from '#/cron';
|
||||
import { IEventSink } from '#/eventSink';
|
||||
import { ExternalHooksService, IExternalHooksService } from '#/externalHooks';
|
||||
import { IAgentContextMemoryService } from '#/contextMemory';
|
||||
import { IAgentContextProjectorService } from '#/contextProjector';
|
||||
import { IAgentContextSizeService } from '#/contextSize';
|
||||
import { AgentCronService } from '#/cron';
|
||||
import { IAgentEventSinkService } from '#/eventSink';
|
||||
import { AgentExternalHooksService, IAgentExternalHooksService } from '#/externalHooks';
|
||||
import { IFlagService } from '#/flag';
|
||||
import { ILLMRequester } from '#/llmRequester';
|
||||
import { IAgentLLMRequesterService } from '#/llmRequester';
|
||||
import { logSeed, resolveLoggingConfig } from '#/log/logConfig';
|
||||
import { LoopService, ILoopService } from '#/loop';
|
||||
import { AgentLoopService, IAgentLoopService } from '#/loop';
|
||||
import { IChatProviderFactory } from '#/chatProvider';
|
||||
import { IModelService } from '#/model';
|
||||
import '#/model/index';
|
||||
import { IModelResolver } from '#/modelRuntime';
|
||||
import { IPermissionRulesService, PermissionRulesService } from '#/permissionRules';
|
||||
import { IProfileService, ProfileService } from '#/profile';
|
||||
import { IPromptService } from '#/prompt';
|
||||
import { ISessionModelResolver } from '#/modelRuntime';
|
||||
import { IAgentPermissionRulesService, AgentPermissionRulesService } from '#/permissionRules';
|
||||
import { IAgentProfileService, AgentProfileService } from '#/profile';
|
||||
import { IAgentPromptService } from '#/prompt';
|
||||
import { IProviderService } from '#/provider';
|
||||
import '#/provider/index';
|
||||
import '#/flag/index';
|
||||
import { IReplayBuilderService } from '#/replayBuilder';
|
||||
import { IAgentReplayBuilderService } from '#/replayBuilder';
|
||||
import { ISessionContext } from '#/session-context';
|
||||
import { IAtomicDocumentStore, IStorageService } from '#/storage';
|
||||
import '#/storage/index';
|
||||
import { ITelemetryService } from '#/telemetry';
|
||||
import { IToolExecutor } from '#/toolExecutor';
|
||||
import { IToolRegistry } from '#/toolRegistry';
|
||||
import { ITurnService } from '#/turn';
|
||||
import { IWireRecord } from '#/wireRecord';
|
||||
import { IAgentToolExecutorService } from '#/toolExecutor';
|
||||
import { IAgentToolRegistryService } from '#/toolRegistry';
|
||||
import { IAgentTurnService } from '#/turn';
|
||||
import { IAgentWireRecordService } from '#/wireRecord';
|
||||
|
||||
/**
|
||||
* One schema-valid sample value per **persistable** section, written through
|
||||
|
|
@ -131,7 +131,7 @@ function hookSlot() {
|
|||
|
||||
describe('config slice (every section owner against one shared registry)', () => {
|
||||
let homeDir: string;
|
||||
let core: Scope;
|
||||
let app: Scope;
|
||||
let configPath: string;
|
||||
let disposables: DisposableStore;
|
||||
let ix: TestInstantiationService;
|
||||
|
|
@ -145,12 +145,12 @@ describe('config slice (every section owner against one shared registry)', () =>
|
|||
mkdirSync(homeDir, { recursive: true });
|
||||
configPath = join(homeDir, 'config.toml');
|
||||
|
||||
// Real, file-backed config. Constructing the Core scope eager-loads
|
||||
// Real, file-backed config. Constructing the App scope eager-loads
|
||||
// `IModelService`, which registers the `models` section + overlay.
|
||||
core = bootstrap({ homeDir }, logSeed(resolveLoggingConfig({ homeDir, env: process.env }))).core;
|
||||
app = bootstrap({ homeDir }, logSeed(resolveLoggingConfig({ homeDir, env: process.env }))).app;
|
||||
|
||||
const registry = core.accessor.get(IConfigRegistry);
|
||||
const config = core.accessor.get(IConfigService);
|
||||
const registry = app.accessor.get(IConfigRegistry);
|
||||
const config = app.accessor.get(IConfigService);
|
||||
|
||||
// Construct the Agent-scope owners against the SAME registry/service. Their
|
||||
// non-config collaborators are stubbed: this example isolates the config
|
||||
|
|
@ -163,79 +163,79 @@ describe('config slice (every section owner against one shared registry)', () =>
|
|||
reg.defineInstance(IConfigService, config);
|
||||
|
||||
// Collaborators touched during owner construction, with real shape.
|
||||
reg.definePartialInstance(IWireRecord, {
|
||||
reg.definePartialInstance(IAgentWireRecordService, {
|
||||
register: () => toDisposable(() => {}),
|
||||
append: () => {},
|
||||
hooks: { onRestoredRecord: hookSlot(), onResumeEnded: hookSlot() },
|
||||
});
|
||||
reg.definePartialInstance(IContextMemory, { hooks: { onSpliced: hookSlot() } });
|
||||
reg.definePartialInstance(IToolExecutor, {
|
||||
reg.definePartialInstance(IAgentContextMemoryService, { hooks: { onSpliced: hookSlot() } });
|
||||
reg.definePartialInstance(IAgentToolExecutorService, {
|
||||
// `OrderedHookSlot` is a class with private members, so the stub is
|
||||
// shaped as a `HookSlot` and cast to the declared slot type.
|
||||
hooks: {
|
||||
onWillExecuteTool: hookSlot(),
|
||||
onDidExecuteTool: hookSlot(),
|
||||
} as unknown as IToolExecutor['hooks'],
|
||||
} as unknown as IAgentToolExecutorService['hooks'],
|
||||
});
|
||||
reg.definePartialInstance(IModelResolver, { defaultModel: 'mock-model' });
|
||||
reg.definePartialInstance(ISessionModelResolver, { defaultModel: 'mock-model' });
|
||||
reg.definePartialInstance(ISessionContext, {
|
||||
metaScope: 'sessions/demo/demo/session-meta',
|
||||
sessionDir: homeDir,
|
||||
});
|
||||
reg.definePartialInstance(ITurnService, { getActiveTurn: () => undefined });
|
||||
reg.definePartialInstance(IAgentTurnService, { getActiveTurn: () => undefined });
|
||||
|
||||
// Collaborators declared but not touched during construction — empty
|
||||
// stubs keep the container strict-clean (no "unknown service" warnings).
|
||||
reg.definePartialInstance(IEventSink, {});
|
||||
reg.definePartialInstance(IAgentEventSinkService, {});
|
||||
reg.definePartialInstance(ITelemetryService, { track: () => {} });
|
||||
reg.definePartialInstance(IPromptService, {});
|
||||
reg.definePartialInstance(IAgentPromptService, {});
|
||||
reg.definePartialInstance(IAtomicDocumentStore, {});
|
||||
reg.definePartialInstance(IStorageService, {});
|
||||
reg.definePartialInstance(IToolRegistry, {});
|
||||
reg.definePartialInstance(IReplayBuilderService, {});
|
||||
reg.definePartialInstance(IAgentToolRegistryService, {});
|
||||
reg.definePartialInstance(IAgentReplayBuilderService, {});
|
||||
reg.definePartialInstance(IChatProviderFactory, {});
|
||||
reg.definePartialInstance(IContextProjector, {});
|
||||
reg.definePartialInstance(IContextSizeService, {});
|
||||
reg.definePartialInstance(ILLMRequester, {});
|
||||
reg.definePartialInstance(IAgentContextProjectorService, {});
|
||||
reg.definePartialInstance(IAgentContextSizeService, {});
|
||||
reg.definePartialInstance(IAgentLLMRequesterService, {});
|
||||
|
||||
// Real Agent-scope section owners. `ICronService` is constructed via
|
||||
// Real Agent-scope section owners. `IAgentCronService` is constructed via
|
||||
// `createInstance` below (not here) so we can pass `{ isSubagent: true }`
|
||||
// and keep its runtime scheduler/tool registration from starting.
|
||||
reg.define(IExternalHooksService, ExternalHooksService);
|
||||
reg.define(IPermissionRulesService, PermissionRulesService);
|
||||
reg.define(IProfileService, ProfileService);
|
||||
reg.define(IBackgroundService, BackgroundService);
|
||||
reg.define(ILoopService, LoopService);
|
||||
reg.define(IAgentExternalHooksService, AgentExternalHooksService);
|
||||
reg.define(IAgentPermissionRulesService, AgentPermissionRulesService);
|
||||
reg.define(IAgentProfileService, AgentProfileService);
|
||||
reg.define(IAgentBackgroundService, AgentBackgroundService);
|
||||
reg.define(IAgentLoopService, AgentLoopService);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
disposables.dispose();
|
||||
core.dispose();
|
||||
app.dispose();
|
||||
});
|
||||
|
||||
test('every section owner registers its section into the shared registry', async () => {
|
||||
const registry = core.accessor.get(IConfigRegistry);
|
||||
const config = core.accessor.get(IConfigService);
|
||||
const registry = app.accessor.get(IConfigRegistry);
|
||||
const config = app.accessor.get(IConfigService);
|
||||
await config.ready;
|
||||
|
||||
// Resolve the Core owners and touch each one: delayed Core services return
|
||||
// Resolve the App owners and touch each one: delayed App services return
|
||||
// a lazy proxy, so reading a method forces construction (and thus the
|
||||
// `registerSection` call). `IModelService` is eager but still needs a `get`.
|
||||
core.accessor.get(IModelService).list();
|
||||
core.accessor.get(IProviderService).list();
|
||||
core.accessor.get(IFlagService).snapshot();
|
||||
app.accessor.get(IModelService).list();
|
||||
app.accessor.get(IProviderService).list();
|
||||
app.accessor.get(IFlagService).snapshot();
|
||||
|
||||
// Construct the Agent owners — each registers its section(s).
|
||||
ix.get(IBackgroundService);
|
||||
ix.get(IPermissionRulesService);
|
||||
ix.get(IProfileService);
|
||||
ix.get(IExternalHooksService);
|
||||
ix.get(ILoopService);
|
||||
ix.get(IAgentBackgroundService);
|
||||
ix.get(IAgentPermissionRulesService);
|
||||
ix.get(IAgentProfileService);
|
||||
ix.get(IAgentExternalHooksService);
|
||||
ix.get(IAgentLoopService);
|
||||
// `isSubagent: true` keeps the cron scheduler/tool registration from
|
||||
// starting — only the `cron` config-section registration is relevant here.
|
||||
ix.createInstance(CronService, { isSubagent: true });
|
||||
ix.createInstance(AgentCronService, { isSubagent: true });
|
||||
|
||||
const registered = registry
|
||||
.listSections()
|
||||
|
|
@ -253,19 +253,19 @@ describe('config slice (every section owner against one shared registry)', () =>
|
|||
});
|
||||
|
||||
test('writes every persistable section through config and round-trips the file', async () => {
|
||||
const config = core.accessor.get(IConfigService);
|
||||
const config = app.accessor.get(IConfigService);
|
||||
await config.ready;
|
||||
|
||||
// Resolve every owner so its section is registered before writing.
|
||||
core.accessor.get(IModelService).list();
|
||||
core.accessor.get(IProviderService).list();
|
||||
core.accessor.get(IFlagService).snapshot();
|
||||
ix.get(IBackgroundService);
|
||||
ix.get(IPermissionRulesService);
|
||||
ix.get(IProfileService);
|
||||
ix.get(IExternalHooksService);
|
||||
ix.get(ILoopService);
|
||||
ix.createInstance(CronService, { isSubagent: true });
|
||||
app.accessor.get(IModelService).list();
|
||||
app.accessor.get(IProviderService).list();
|
||||
app.accessor.get(IFlagService).snapshot();
|
||||
ix.get(IAgentBackgroundService);
|
||||
ix.get(IAgentPermissionRulesService);
|
||||
ix.get(IAgentProfileService);
|
||||
ix.get(IAgentExternalHooksService);
|
||||
ix.get(IAgentLoopService);
|
||||
ix.createInstance(AgentCronService, { isSubagent: true });
|
||||
|
||||
// Write each persistable section through IConfigService — every owner's
|
||||
// value is validated, env-stripped, and persisted to config.toml.
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@
|
|||
* scoped registry is cleared and re-populated explicitly in `beforeEach` rather
|
||||
* than relying on import-order side effects.
|
||||
*
|
||||
* `IInteractionService` is the only Service that owns state — a pending set
|
||||
* `ISessionInteractionService` is the only Service that owns state — a pending set
|
||||
* plus a recently-resolved ledger — and it is domain-agnostic.
|
||||
* `IApprovalService` and `IQuestionService` are zero-state typed facades over
|
||||
* `ISessionApprovalService` and `ISessionQuestionService` are zero-state typed facades over
|
||||
* it: they tag each request with `kind: 'approval'` / `kind: 'question'`,
|
||||
* rename the resolve verb (`decide` / `answer` → `respond`), and cast the
|
||||
* stored payload back to the typed request on `listPending`.
|
||||
|
|
@ -41,9 +41,9 @@ import {
|
|||
type Scope,
|
||||
} from '#/_base/di/scope';
|
||||
import { createScopedTestHost, type ScopedTestHost } from '#/_base/di/test';
|
||||
import { type ApprovalRequest, ApprovalService, IApprovalService } from '#/approval';
|
||||
import { IInteractionService, InteractionService } from '#/interaction';
|
||||
import { type QuestionRequest, IQuestionService, QuestionService } from '#/question';
|
||||
import { type ApprovalRequest, SessionApprovalService, ISessionApprovalService } from '#/approval';
|
||||
import { ISessionInteractionService, SessionInteractionService } from '#/interaction';
|
||||
import { type QuestionRequest, ISessionQuestionService, SessionQuestionService } from '#/question';
|
||||
|
||||
const display: ToolInputDisplay = { kind: 'command', command: 'rm -rf /tmp/demo' };
|
||||
|
||||
|
|
@ -70,9 +70,9 @@ describe('interaction kernel + approval/question facades (Session scope)', () =>
|
|||
|
||||
beforeEach(() => {
|
||||
_clearScopedRegistryForTests();
|
||||
registerScopedService(LifecycleScope.Session, IInteractionService, InteractionService, InstantiationType.Delayed, 'interaction');
|
||||
registerScopedService(LifecycleScope.Session, IApprovalService, ApprovalService, InstantiationType.Delayed, 'approval');
|
||||
registerScopedService(LifecycleScope.Session, IQuestionService, QuestionService, InstantiationType.Delayed, 'question');
|
||||
registerScopedService(LifecycleScope.Session, ISessionInteractionService, SessionInteractionService, InstantiationType.Delayed, 'interaction');
|
||||
registerScopedService(LifecycleScope.Session, ISessionApprovalService, SessionApprovalService, InstantiationType.Delayed, 'approval');
|
||||
registerScopedService(LifecycleScope.Session, ISessionQuestionService, SessionQuestionService, InstantiationType.Delayed, 'question');
|
||||
|
||||
disposables = new DisposableStore();
|
||||
host = createScopedTestHost();
|
||||
|
|
@ -84,7 +84,7 @@ describe('interaction kernel + approval/question facades (Session scope)', () =>
|
|||
});
|
||||
|
||||
test('blocking: approval.request parks until decide resolves the Promise', async () => {
|
||||
const approvals = session.accessor.get(IApprovalService);
|
||||
const approvals = session.accessor.get(ISessionApprovalService);
|
||||
|
||||
// The caller (e.g. a tool) awaits the decision. Nothing resolves yet.
|
||||
const decision = approvals.request(approval('bash-1'));
|
||||
|
|
@ -97,8 +97,8 @@ describe('interaction kernel + approval/question facades (Session scope)', () =>
|
|||
});
|
||||
|
||||
test('non-blocking: question.enqueue returns immediately; the answer streams over onDidResolve', () => {
|
||||
const interaction = session.accessor.get(IInteractionService);
|
||||
const questions = session.accessor.get(IQuestionService);
|
||||
const interaction = session.accessor.get(ISessionInteractionService);
|
||||
const questions = session.accessor.get(ISessionQuestionService);
|
||||
|
||||
// Edge callers observe outcomes through the stream instead of awaiting.
|
||||
const resolved: { id: string; response: unknown }[] = [];
|
||||
|
|
@ -116,9 +116,9 @@ describe('interaction kernel + approval/question facades (Session scope)', () =>
|
|||
});
|
||||
|
||||
test('one kernel backs both facades; onDidChange announces every mutation', () => {
|
||||
const interaction = session.accessor.get(IInteractionService);
|
||||
const approvals = session.accessor.get(IApprovalService);
|
||||
const questions = session.accessor.get(IQuestionService);
|
||||
const interaction = session.accessor.get(ISessionInteractionService);
|
||||
const approvals = session.accessor.get(ISessionApprovalService);
|
||||
const questions = session.accessor.get(ISessionQuestionService);
|
||||
|
||||
let changes = 0;
|
||||
disposables.add(interaction.onDidChange(() => changes++));
|
||||
|
|
@ -139,8 +139,8 @@ describe('interaction kernel + approval/question facades (Session scope)', () =>
|
|||
test('Session scope isolates brokers: a request parked in A is invisible to B', async () => {
|
||||
const sessionB = host.child(LifecycleScope.Session, 'session-b');
|
||||
|
||||
const approvalsA = session.accessor.get(IApprovalService);
|
||||
const approvalsB = sessionB.accessor.get(IApprovalService);
|
||||
const approvalsA = session.accessor.get(ISessionApprovalService);
|
||||
const approvalsB = sessionB.accessor.get(ISessionApprovalService);
|
||||
console.log('1) distinct broker instances per session:', approvalsA !== approvalsB);
|
||||
|
||||
const decisionA = approvalsA.request(approval('bash-1'));
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
* *triggers* the refresh explicitly (it is not auto-chained inside login),
|
||||
* then observes the new aliases arrive through config.
|
||||
*
|
||||
* Everything runs against the real Core-scope Services **and** the real OAuth
|
||||
* Everything runs against the real App-scope Services **and** the real OAuth
|
||||
* clients — `KimiOAuthToolkit` (device-code protocol + token persistence) and
|
||||
* `fetchManagedKimiCodeModels` (the `/models` request) are not stubbed. The
|
||||
* only thing faked is the wire itself: `globalThis.fetch` is replaced with a
|
||||
|
|
@ -138,7 +138,7 @@ async function waitUntil(predicate: () => boolean, timeoutMs = 2000): Promise<vo
|
|||
describe('oauth → modelCatalog slice (request-layer fetch mock, real clients)', () => {
|
||||
let homeDir: string;
|
||||
let caseDir: string;
|
||||
let core: Scope | undefined;
|
||||
let app: Scope | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
const resolved = process.env['KIMI_CODE_HOME'];
|
||||
|
|
@ -155,23 +155,23 @@ describe('oauth → modelCatalog slice (request-layer fetch mock, real clients)'
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
core?.dispose();
|
||||
core = undefined;
|
||||
app?.dispose();
|
||||
app = undefined;
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function buildCore(): Scope {
|
||||
function buildApp(): Scope {
|
||||
return bootstrap(
|
||||
{ homeDir: caseDir },
|
||||
logSeed(resolveLoggingConfig({ homeDir: caseDir, env: process.env })),
|
||||
).core;
|
||||
).app;
|
||||
}
|
||||
|
||||
test('device-code login provisions the provider credential through config.onDidChange', async () => {
|
||||
core = buildCore();
|
||||
const config = core.accessor.get(IConfigService);
|
||||
const oauth = core.accessor.get(IOAuthService);
|
||||
const providers = core.accessor.get(IProviderService);
|
||||
app = buildApp();
|
||||
const config = app.accessor.get(IConfigService);
|
||||
const oauth = app.accessor.get(IOAuthService);
|
||||
const providers = app.accessor.get(IProviderService);
|
||||
await config.ready;
|
||||
providers.list();
|
||||
|
||||
|
|
@ -199,11 +199,11 @@ describe('oauth → modelCatalog slice (request-layer fetch mock, real clients)'
|
|||
});
|
||||
|
||||
test('refreshOAuthProviderModels fetches /models internally and lands aliases through config.onDidChange', async () => {
|
||||
core = buildCore();
|
||||
const config = core.accessor.get(IConfigService);
|
||||
const oauth = core.accessor.get(IOAuthService);
|
||||
const providers = core.accessor.get(IProviderService);
|
||||
const models = core.accessor.get(IModelService);
|
||||
app = buildApp();
|
||||
const config = app.accessor.get(IConfigService);
|
||||
const oauth = app.accessor.get(IOAuthService);
|
||||
const providers = app.accessor.get(IProviderService);
|
||||
const models = app.accessor.get(IModelService);
|
||||
await config.ready;
|
||||
providers.list();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Builds a flat container that runs both services for real (neither has
|
||||
* cross-domain collaborators, so nothing is stubbed). `ILogService` writes
|
||||
* through the Core console writer; `ITelemetryService` fans events out to a
|
||||
* through the App console writer; `ITelemetryService` fans events out to a
|
||||
* console appender while merging bound context. The two compose: a child
|
||||
* logger and a context-scoped telemetry both carry their bound fields into
|
||||
* the output.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const WIRE_KEY = 'example';
|
|||
|
||||
describe('persistence module (Store → Storage → backend, real ~/.kimi-code files)', () => {
|
||||
let homeDir: string;
|
||||
let core: Scope;
|
||||
let app: Scope;
|
||||
|
||||
beforeEach(() => {
|
||||
const resolved = process.env['KIMI_CODE_HOME'];
|
||||
|
|
@ -64,17 +64,17 @@ describe('persistence module (Store → Storage → backend, real ~/.kimi-code f
|
|||
}
|
||||
homeDir = resolved;
|
||||
mkdirSync(homeDir, { recursive: true });
|
||||
core = bootstrap({ homeDir }).core;
|
||||
app = bootstrap({ homeDir }).app;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
core.dispose();
|
||||
app.dispose();
|
||||
});
|
||||
|
||||
test('typed Store → raw Storage bytes → real file path', async () => {
|
||||
// 1) Atomic document, TOML codec → config.toml
|
||||
const tomlDocs = core.accessor.get(IAtomicTomlDocumentStore);
|
||||
const configBytes = core.accessor.get(IStorageService);
|
||||
const tomlDocs = app.accessor.get(IAtomicTomlDocumentStore);
|
||||
const configBytes = app.accessor.get(IStorageService);
|
||||
const configValue = { theme: 'dark', telemetry: { enabled: true } };
|
||||
await tomlDocs.set('', 'config.toml', configValue);
|
||||
console.log('1) config.toml (atomic doc, TOML):');
|
||||
|
|
@ -86,8 +86,8 @@ describe('persistence module (Store → Storage → backend, real ~/.kimi-code f
|
|||
console.log(' path :', join(homeDir, 'config.toml'));
|
||||
|
||||
// 2) Atomic document, JSON codec → sessions/.../session-meta/state.json
|
||||
const docs = core.accessor.get(IAtomicDocumentStore);
|
||||
const docBytes = core.accessor.get(IAtomicDocumentStorage);
|
||||
const docs = app.accessor.get(IAtomicDocumentStore);
|
||||
const docBytes = app.accessor.get(IAtomicDocumentStorage);
|
||||
const metaScope = 'sessions/example/s-example/session-meta';
|
||||
const meta = {
|
||||
id: 's-example',
|
||||
|
|
@ -103,8 +103,8 @@ describe('persistence module (Store → Storage → backend, real ~/.kimi-code f
|
|||
console.log(' path :', join(homeDir, metaScope, 'state.json'));
|
||||
|
||||
// 3) Append log, JSONL framing → wire/<hash>.jsonl
|
||||
const logs = core.accessor.get(IAppendLogStore);
|
||||
const logBytes = core.accessor.get(IAppendLogStorage);
|
||||
const logs = app.accessor.get(IAppendLogStore);
|
||||
const logBytes = app.accessor.get(IAppendLogStorage);
|
||||
const key = WIRE_KEY;
|
||||
logs.append('wire', key, { type: 'metadata', protocol_version: '1.5' });
|
||||
logs.append('wire', key, { type: 'swarm_mode.enter', trigger: 'manual' });
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
* Scenario: the **DI Scope** foundation — how resolution follows the tree.
|
||||
*
|
||||
* Not a business slice but the model every other slice rests on. Two rules,
|
||||
* shown with real services: a Core-scoped service (`ILogService`) resolves to
|
||||
* the same instance whether you ask the Core scope or a child Session scope
|
||||
* shown with real services: a App-scoped service (`ILogService`) resolves to
|
||||
* the same instance whether you ask the App scope or a child Session scope
|
||||
* (it is found by walking up), while a Session-scoped service
|
||||
* (`ISessionMetadata`) is one distinct instance per session, so two sessions
|
||||
* hold independent state. Loads only `log` and `session-metadata`.
|
||||
|
|
@ -31,9 +31,9 @@ function diskStorageSeed(homeDir: string): ScopeSeed {
|
|||
return [[IAtomicDocumentStorage as ServiceIdentifier<unknown>, new FileStorageService(homeDir)]];
|
||||
}
|
||||
|
||||
describe('di scope foundation (Core singletons vs. per-Session instances)', () => {
|
||||
describe('di scope foundation (App singletons vs. per-Session instances)', () => {
|
||||
let homeDir: string;
|
||||
let core: Scope;
|
||||
let app: Scope;
|
||||
|
||||
beforeEach(() => {
|
||||
const resolved = process.env['KIMI_CODE_HOME'];
|
||||
|
|
@ -42,18 +42,18 @@ describe('di scope foundation (Core singletons vs. per-Session instances)', () =
|
|||
}
|
||||
homeDir = resolved;
|
||||
mkdirSync(homeDir, { recursive: true });
|
||||
core = bootstrap({}, [
|
||||
app = bootstrap({}, [
|
||||
...logSeed(resolveLoggingConfig({ homeDir, env: process.env })),
|
||||
...diskStorageSeed(homeDir),
|
||||
]).core;
|
||||
]).app;
|
||||
});
|
||||
afterEach(() => {
|
||||
core.dispose();
|
||||
app.dispose();
|
||||
});
|
||||
|
||||
function createSession(sessionId: string): Scope {
|
||||
const sessionDir = join(homeDir, 'sessions', 'example', sessionId);
|
||||
return core.createChild(LifecycleScope.Session, sessionId, {
|
||||
return app.createChild(LifecycleScope.Session, sessionId, {
|
||||
extra: [
|
||||
...sessionContextSeed({
|
||||
_serviceBrand: undefined,
|
||||
|
|
@ -67,14 +67,14 @@ describe('di scope foundation (Core singletons vs. per-Session instances)', () =
|
|||
});
|
||||
}
|
||||
|
||||
test('Core services are shared; Session services are per-session', async () => {
|
||||
test('App services are shared; Session services are per-session', async () => {
|
||||
console.log('KIMI_CODE_HOME =', homeDir);
|
||||
const sessionA = createSession('scope-a');
|
||||
const sessionB = createSession('scope-b');
|
||||
|
||||
const logFromCore = core.accessor.get(ILogService);
|
||||
const logFromCore = app.accessor.get(ILogService);
|
||||
const logFromSession = sessionA.accessor.get(ILogService);
|
||||
console.log('Core ILogService shared across scopes:', logFromCore === logFromSession);
|
||||
console.log('App ILogService shared across scopes:', logFromCore === logFromSession);
|
||||
|
||||
const metaA = sessionA.accessor.get(ISessionMetadata);
|
||||
const metaB = sessionB.accessor.get(ISessionMetadata);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ interface SeedMeta {
|
|||
|
||||
describe('session-index module (business Store over storage Stores)', () => {
|
||||
let homeDir: string;
|
||||
let core: Scope;
|
||||
let app: Scope;
|
||||
let sessionsScope: string;
|
||||
let docs: IAtomicDocumentStore;
|
||||
let index: ISessionIndex;
|
||||
|
|
@ -58,11 +58,11 @@ describe('session-index module (business Store over storage Stores)', () => {
|
|||
homeDir = resolved;
|
||||
mkdirSync(homeDir, { recursive: true });
|
||||
|
||||
core = bootstrap({ homeDir }).core;
|
||||
const layout = core.accessor.get(IBootstrapService);
|
||||
app = bootstrap({ homeDir }).app;
|
||||
const layout = app.accessor.get(IBootstrapService);
|
||||
sessionsScope = relative(layout.homeDir, layout.sessionsDir);
|
||||
docs = core.accessor.get(IAtomicDocumentStore);
|
||||
index = core.accessor.get(ISessionIndex);
|
||||
docs = app.accessor.get(IAtomicDocumentStore);
|
||||
index = app.accessor.get(ISessionIndex);
|
||||
|
||||
await seed('ws-a', 's1', {
|
||||
title: 'first session',
|
||||
|
|
@ -91,7 +91,7 @@ describe('session-index module (business Store over storage Stores)', () => {
|
|||
});
|
||||
|
||||
afterEach(() => {
|
||||
core.dispose();
|
||||
app.dispose();
|
||||
});
|
||||
|
||||
async function seed(workspaceId: string, sessionId: string, meta: SeedMeta): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
* Scenario: the **session** slice — `session-lifecycle` + `session-metadata`.
|
||||
*
|
||||
* Shows the session as a durable, tracked entity and how the slice's domains
|
||||
* compose: `ISessionLifecycleService` (Core) creates Session child scopes —
|
||||
* compose: `ISessionLifecycleService` (App) creates Session child scopes —
|
||||
* seeding each with its identity and storage and materializing its metadata —
|
||||
* and tracks the live set, while each session's `ISessionMetadata` (Session)
|
||||
* reads and updates the persisted document through the Core `storage` service.
|
||||
* reads and updates the persisted document through the App `storage` service.
|
||||
* The host is the production `bootstrap` composition root (real file-backed
|
||||
* storage rooted under `.vitest-results/kimi-code-{timestamp}/`); only the
|
||||
* slice's barrels are imported, so nothing outside it is loaded.
|
||||
|
|
@ -33,7 +33,7 @@ function diskStorageSeed(homeDir: string): ScopeSeed {
|
|||
|
||||
describe('session slice (session-lifecycle + session-metadata)', () => {
|
||||
let homeDir: string;
|
||||
let core: Scope;
|
||||
let app: Scope;
|
||||
|
||||
beforeEach(() => {
|
||||
const resolved = process.env['KIMI_CODE_HOME'];
|
||||
|
|
@ -42,18 +42,18 @@ describe('session slice (session-lifecycle + session-metadata)', () => {
|
|||
}
|
||||
homeDir = resolved;
|
||||
mkdirSync(homeDir, { recursive: true });
|
||||
core = bootstrap({}, [
|
||||
app = bootstrap({}, [
|
||||
...logSeed(resolveLoggingConfig({ homeDir, env: process.env })),
|
||||
...diskStorageSeed(homeDir),
|
||||
]).core;
|
||||
]).app;
|
||||
});
|
||||
afterEach(() => {
|
||||
core.dispose();
|
||||
app.dispose();
|
||||
});
|
||||
|
||||
test('creates, tracks, persists, and closes sessions', async () => {
|
||||
console.log('KIMI_CODE_HOME =', homeDir);
|
||||
const lifecycle = core.accessor.get(ISessionLifecycleService);
|
||||
const lifecycle = app.accessor.get(ISessionLifecycleService);
|
||||
|
||||
const first = await lifecycle.create({ sessionId: 's1', workDir: homeDir });
|
||||
await lifecycle.create({ sessionId: 's2', workDir: homeDir });
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Scenario: the **wire-record** module — a durable-record + replay chain built
|
||||
* on the append-log Store.
|
||||
*
|
||||
* Shows how a real Domain Service (`IWireRecord` / `WireRecordService`)
|
||||
* Shows how a real Domain Service (`IAgentWireRecordService` / `AgentWireRecordService`)
|
||||
* aggregates the `IAppendLogStore` access pattern into a complete engine call
|
||||
* chain: `append` stamps and persists records (writing a `metadata` header
|
||||
* first), and `restore` reads the log back and replays each record through the
|
||||
|
|
@ -11,8 +11,8 @@
|
|||
* `swarm` domain's `WireRecordMap` declaration merge.
|
||||
*
|
||||
* Persistence is gated on the `homedir` option: the scoped-registered
|
||||
* `IWireRecord` passes no options and is therefore in-memory only, so this
|
||||
* scenario constructs the real `WireRecordService` with `createInstance(...,
|
||||
* `IAgentWireRecordService` passes no options and is therefore in-memory only, so this
|
||||
* scenario constructs the real `AgentWireRecordService` with `createInstance(...,
|
||||
* { homedir })` — the same way production wires a persisting wire record —
|
||||
* resolving its `IAppendLogStore` dependency from the container. The resumers
|
||||
* are small side callbacks (not Services). All resolved Services come from
|
||||
|
|
@ -26,7 +26,7 @@ import { afterEach, beforeEach, describe, test } from 'vitest';
|
|||
import { DisposableStore } from '#/_base/di/lifecycle';
|
||||
import { createServices, type TestInstantiationService } from '#/_base/di/test';
|
||||
import { FileStorageService, IAppendLogStorage, IAppendLogStore, AppendLogStore } from '#/storage';
|
||||
import { WireRecordService, type IWireRecord } from '#/wireRecord';
|
||||
import { AgentWireRecordService, type IAgentWireRecordService } from '#/wireRecord';
|
||||
import '#/swarm/swarm';
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
|
|
@ -61,7 +61,7 @@ describe('wire-record module (durable record + replay over IAppendLogStore)', ()
|
|||
const logBytes = ix.get(IAppendLogStorage);
|
||||
|
||||
// --- writer side: append records, which persist to wire/<hash>.jsonl ---
|
||||
const writer: IWireRecord = ix.createInstance(WireRecordService, { homedir: homeDir });
|
||||
const writer: IAgentWireRecordService = ix.createInstance(AgentWireRecordService, { homedir: homeDir });
|
||||
|
||||
writer.append({ type: 'swarm_mode.enter', trigger: 'manual' });
|
||||
writer.append({ type: 'swarm_mode.exit' });
|
||||
|
|
@ -77,7 +77,7 @@ describe('wire-record module (durable record + replay over IAppendLogStore)', ()
|
|||
|
||||
// --- reader side: a fresh instance on the same log replays the records ---
|
||||
const replayed: string[] = [];
|
||||
const reader: IWireRecord = ix.createInstance(WireRecordService, { homedir: homeDir });
|
||||
const reader: IAgentWireRecordService = ix.createInstance(AgentWireRecordService, { homedir: homeDir });
|
||||
reader.register('swarm_mode.enter', (rec) => {
|
||||
replayed.push(`enter(trigger=${rec.trigger})`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { DisposableStore, type IDisposable } from './lifecycle';
|
|||
import { ServiceCollection } from './serviceCollection';
|
||||
|
||||
export enum LifecycleScope {
|
||||
Core = 0,
|
||||
App = 0,
|
||||
Session = 1,
|
||||
Agent = 2,
|
||||
}
|
||||
|
|
@ -119,11 +119,11 @@ export class Scope implements IDisposable {
|
|||
};
|
||||
}
|
||||
|
||||
static createCore(options: ScopeOptions = {}): Scope {
|
||||
const kind = LifecycleScope.Core;
|
||||
static createApp(options: ScopeOptions = {}): Scope {
|
||||
const kind = LifecycleScope.App;
|
||||
const collection = buildCollection(kind, options.extra);
|
||||
const instantiation = new InstantiationService(collection, true);
|
||||
return new Scope(options.id ?? 'core', kind, instantiation);
|
||||
return new Scope(options.id ?? 'app', kind, instantiation);
|
||||
}
|
||||
|
||||
private _assertNotDisposed(): void {
|
||||
|
|
@ -174,6 +174,6 @@ export class Scope implements IDisposable {
|
|||
}
|
||||
}
|
||||
|
||||
export function createCoreScope(options: ScopeOptions = {}): Scope {
|
||||
return Scope.createCore(options);
|
||||
export function createAppScope(options: ScopeOptions = {}): Scope {
|
||||
return Scope.createApp(options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,27 +13,27 @@ export type {
|
|||
} from './testInstantiationService';
|
||||
|
||||
import { type ServiceIdentifier } from './instantiation';
|
||||
import { createCoreScope, LifecycleScope, Scope, type ScopeSeed } from './scope';
|
||||
import { createAppScope, LifecycleScope, Scope, type ScopeSeed } from './scope';
|
||||
|
||||
export interface ScopedTestHost {
|
||||
readonly core: Scope;
|
||||
readonly app: Scope;
|
||||
child(kind: LifecycleScope, id: string, stubs?: ScopeSeed): Scope;
|
||||
childOf(parent: Scope, kind: LifecycleScope, id: string, stubs?: ScopeSeed): Scope;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export function createScopedTestHost(coreStubs: ScopeSeed = []): ScopedTestHost {
|
||||
const core = createCoreScope({ extra: coreStubs });
|
||||
export function createScopedTestHost(appStubs: ScopeSeed = []): ScopedTestHost {
|
||||
const app = createAppScope({ extra: appStubs });
|
||||
return {
|
||||
core,
|
||||
app,
|
||||
child(kind, id, stubs = []) {
|
||||
return core.createChild(kind, id, { extra: stubs });
|
||||
return app.createChild(kind, id, { extra: stubs });
|
||||
},
|
||||
childOf(parent, kind, id, stubs = []) {
|
||||
return parent.createChild(kind, id, { extra: stubs });
|
||||
},
|
||||
dispose() {
|
||||
core.dispose();
|
||||
app.dispose();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from '#/_base/di/scope';
|
||||
import { ISessionContext } from '#/session-context';
|
||||
import { ISessionMetadata } from '#/session-metadata';
|
||||
import { IWireRecord, WireRecordService } from '#/wireRecord';
|
||||
import { IAgentWireRecordService, AgentWireRecordService } from '#/wireRecord';
|
||||
|
||||
import { type CreateAgentOptions, IAgentLifecycleService } from './agentLifecycle';
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ export class AgentLifecycleService extends Disposable implements IAgentLifecycle
|
|||
LifecycleScope.Agent,
|
||||
agentId,
|
||||
{
|
||||
extra: [[IWireRecord, new SyncDescriptor(WireRecordService, [{ homedir: agentHomedir }])]],
|
||||
extra: [[IAgentWireRecordService, new SyncDescriptor(AgentWireRecordService, [{ homedir: agentHomedir }])]],
|
||||
},
|
||||
);
|
||||
this.handles.set(agentId, handle);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
/**
|
||||
* `agentFs` domain (L1) — the Agent's filesystem.
|
||||
*
|
||||
* Defines the `IAgentFileSystem` that business code injects to read and write
|
||||
* Defines the `ISessionAgentFileSystem` that business code injects to read and write
|
||||
* files inside the Agent's execution environment. Session-scoped and backed by
|
||||
* the session `IKaos`; business code depends on `IAgentFileSystem` only.
|
||||
* the session `IKaos`; business code depends on `ISessionAgentFileSystem` only.
|
||||
*/
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
|
@ -18,7 +18,7 @@ export interface AgentFileStat {
|
|||
readonly ino?: number;
|
||||
}
|
||||
|
||||
export interface IAgentFileSystem {
|
||||
export interface ISessionAgentFileSystem {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly cwd: string;
|
||||
|
|
@ -38,8 +38,8 @@ export interface IAgentFileSystem {
|
|||
path: string,
|
||||
options?: { readonly parents?: boolean; readonly existOk?: boolean },
|
||||
): Promise<void>;
|
||||
withCwd(cwd: string): IAgentFileSystem;
|
||||
withCwd(cwd: string): ISessionAgentFileSystem;
|
||||
}
|
||||
|
||||
export const IAgentFileSystem: ServiceIdentifier<IAgentFileSystem> =
|
||||
createDecorator<IAgentFileSystem>('agentFileSystem');
|
||||
export const ISessionAgentFileSystem: ServiceIdentifier<ISessionAgentFileSystem> =
|
||||
createDecorator<ISessionAgentFileSystem>('sessionAgentFileSystem');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* `agentFs` domain (L1) — `IAgentFileSystem` implementation.
|
||||
* `agentFs` domain (L1) — `ISessionAgentFileSystem` implementation.
|
||||
*
|
||||
* Focused file-IO surface over the session execution environment (`IKaos.backend`).
|
||||
* Relative-path resolution (in the target path style) and symlink-safe glob are
|
||||
|
|
@ -12,7 +12,7 @@ import { InstantiationType } from '#/_base/di/extensions';
|
|||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { IKaos, type StatResult } from '#/kaos';
|
||||
|
||||
import { type AgentFileStat, IAgentFileSystem } from './agentFs';
|
||||
import { type AgentFileStat, ISessionAgentFileSystem } from './agentFs';
|
||||
|
||||
const S_IFMT = 0o170000;
|
||||
const S_IFREG = 0o100000;
|
||||
|
|
@ -28,7 +28,7 @@ function basename(p: string): string {
|
|||
return parts[parts.length - 1] ?? p;
|
||||
}
|
||||
|
||||
export class AgentFileSystem implements IAgentFileSystem {
|
||||
export class SessionAgentFileSystem implements ISessionAgentFileSystem {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(@IKaos private readonly kaos: IKaos) {}
|
||||
|
|
@ -96,15 +96,15 @@ export class AgentFileSystem implements IAgentFileSystem {
|
|||
});
|
||||
}
|
||||
|
||||
withCwd(cwd: string): IAgentFileSystem {
|
||||
return new AgentFileSystem(this.kaos.withCwd(cwd));
|
||||
withCwd(cwd: string): ISessionAgentFileSystem {
|
||||
return new SessionAgentFileSystem(this.kaos.withCwd(cwd));
|
||||
}
|
||||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Session,
|
||||
IAgentFileSystem,
|
||||
AgentFileSystem,
|
||||
ISessionAgentFileSystem,
|
||||
SessionAgentFileSystem,
|
||||
InstantiationType.Delayed,
|
||||
'agentFs',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/**
|
||||
* `agentFs` domain (L2) — wire-shaped filesystem operations.
|
||||
*
|
||||
* Defines the `IFsService` that backs the fs REST surface: content search,
|
||||
* Defines the `ISessionFsService` that backs the fs REST surface: content search,
|
||||
* content grep, and git status/diff. It is the higher-level counterpart to
|
||||
* `IAgentFileSystem` (the thin IO primitive): it orchestrates the IO primitive
|
||||
* plus `IProcessRunner` (for `rg` / `git` / `gh`) and returns protocol-shaped
|
||||
* `ISessionAgentFileSystem` (the thin IO primitive): it orchestrates the IO primitive
|
||||
* plus `ISessionProcessRunner` (for `rg` / `git` / `gh`) and returns protocol-shaped
|
||||
* responses. Session-scoped — the scope itself is the session, so no
|
||||
* `sessionId` is threaded through.
|
||||
*/
|
||||
|
|
@ -50,7 +50,7 @@ export interface FsDownloadResolved {
|
|||
readonly modifiedAt: Date;
|
||||
}
|
||||
|
||||
export interface IFsService {
|
||||
export interface ISessionFsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
list(req: FsListRequest): Promise<FsListResponse>;
|
||||
|
|
@ -67,5 +67,5 @@ export interface IFsService {
|
|||
resolveDownload(relPath: string): Promise<FsDownloadResolved>;
|
||||
}
|
||||
|
||||
export const IFsService: ServiceIdentifier<IFsService> =
|
||||
createDecorator<IFsService>('fsService');
|
||||
export const ISessionFsService: ServiceIdentifier<ISessionFsService> =
|
||||
createDecorator<ISessionFsService>('sessionFsService');
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* `agentFs` domain (L2) — `runCommand` helper over `IProcessRunner`.
|
||||
* `agentFs` domain (L2) — `runCommand` helper over `ISessionProcessRunner`.
|
||||
*
|
||||
* Collects a child process's full stdout/stderr and exit code through the
|
||||
* Agent's backend-pluggable `IProcessRunner`, with optional `AbortSignal`
|
||||
* Agent's backend-pluggable `ISessionProcessRunner`, with optional `AbortSignal`
|
||||
* support (the caller decides timeout semantics — git has none, `gh pr view`
|
||||
* uses 5s, `rg` grep uses 30s). Kept separate from `fsService` so it can be
|
||||
* unit-tested with a fake runner.
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
import { type Readable } from 'node:stream';
|
||||
|
||||
import { type IProcess, type IProcessRunner } from '#/process';
|
||||
import { type IProcess, type ISessionProcessRunner } from '#/process';
|
||||
|
||||
export interface RunResult {
|
||||
readonly exitCode: number;
|
||||
|
|
@ -26,7 +26,7 @@ export interface RunCommandOptions {
|
|||
}
|
||||
|
||||
export async function runCommand(
|
||||
runner: IProcessRunner,
|
||||
runner: ISessionProcessRunner,
|
||||
args: readonly string[],
|
||||
options: RunCommandOptions = {},
|
||||
): Promise<RunResult> {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
/**
|
||||
* `agentFs` domain (L2) — `IFsService` implementation.
|
||||
* `agentFs` domain (L2) — `ISessionFsService` implementation.
|
||||
*
|
||||
* Backs the fs REST surface (search / grep / git status / git diff) by
|
||||
* orchestrating `IAgentFileSystem` (file IO) and `IProcessRunner` (`rg` /
|
||||
* orchestrating `ISessionAgentFileSystem` (file IO) and `ISessionProcessRunner` (`rg` /
|
||||
* `git` / `gh`). Bound at Session scope — the workspace root and execution
|
||||
* environment come from the scope, so no `sessionId` is threaded through.
|
||||
*
|
||||
* Path confinement is lexical (`IWorkspaceContext.isWithin`); it does not
|
||||
* Path confinement is lexical (`ISessionWorkspaceContext.isWithin`); it does not
|
||||
* follow symlinks, matching the rest of v2 (`_base/tools/policies/path-access.ts`).
|
||||
*/
|
||||
|
||||
|
|
@ -45,11 +45,11 @@ import ignore, { type Ignore } from 'ignore';
|
|||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { ErrorCodes, KimiError } from '#/errors';
|
||||
import { IProcessRunner } from '#/process';
|
||||
import { IWorkspaceContext } from '#/workspaceContext';
|
||||
import { ISessionProcessRunner } from '#/process';
|
||||
import { ISessionWorkspaceContext } from '#/workspaceContext';
|
||||
|
||||
import { type AgentFileStat, IAgentFileSystem } from './agentFs';
|
||||
import { type FsDownloadResolved, type FsPathResolved, IFsService } from './fs';
|
||||
import { type AgentFileStat, ISessionAgentFileSystem } from './agentFs';
|
||||
import { type FsDownloadResolved, type FsPathResolved, ISessionFsService } from './fs';
|
||||
import { parseNumstat, parsePorcelain, parsePullRequest } from './fsGit';
|
||||
import { runCommand } from './fsProcess';
|
||||
import {
|
||||
|
|
@ -80,7 +80,7 @@ const FS_BINARY_NONPRINTABLE_FRACTION = 0.3;
|
|||
const HIDDEN_NAME_RE = /^\./;
|
||||
const MACOS_NOISE = new Set(['.DS_Store', '.AppleDouble', '.LSOverride']);
|
||||
|
||||
export class FsService implements IFsService {
|
||||
export class SessionFsService implements ISessionFsService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly gitignoreCache = new Map<string, Ignore>();
|
||||
|
|
@ -91,9 +91,9 @@ export class FsService implements IFsService {
|
|||
private rgAvailable: boolean | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContext private readonly workspace: IWorkspaceContext,
|
||||
@IAgentFileSystem private readonly fs: IAgentFileSystem,
|
||||
@IProcessRunner private readonly runner: IProcessRunner,
|
||||
@ISessionWorkspaceContext private readonly workspace: ISessionWorkspaceContext,
|
||||
@ISessionAgentFileSystem private readonly fs: ISessionAgentFileSystem,
|
||||
@ISessionProcessRunner private readonly runner: ISessionProcessRunner,
|
||||
) {}
|
||||
|
||||
async list(req: FsListRequest): Promise<FsListResponse> {
|
||||
|
|
@ -895,7 +895,7 @@ function parseRgJsonOutput(
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers shared by the list/read/stat/mkdir methods. Ported from the v1
|
||||
// `FsService` so the `/api/v1` mirror stays byte-compatible.
|
||||
// `SessionFsService` so the `/api/v1` mirror stays byte-compatible.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isHidden(name: string): boolean {
|
||||
|
|
@ -1074,8 +1074,8 @@ function guessLanguageId(relPath: string): string | undefined {
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Session,
|
||||
IFsService,
|
||||
FsService,
|
||||
ISessionFsService,
|
||||
SessionFsService,
|
||||
InstantiationType.Delayed,
|
||||
'agentFs',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* `agentFs` domain barrel — re-exports the agent-filesystem contract
|
||||
* (`agentFs`) and its scoped service (`agentFsService`), the wire-shaped fs
|
||||
* service (`fs`, `fsService`), and the fs error codes (`errors`). Importing
|
||||
* this barrel registers the `IAgentFileSystem` and `IFsService` bindings into
|
||||
* this barrel registers the `ISessionAgentFileSystem` and `ISessionFsService` bindings into
|
||||
* the scope registry.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* `approval` domain (L7) — session-scope approval broker.
|
||||
*
|
||||
* Defines the public contract of approval brokering: the `ApprovalRequest` /
|
||||
* `ApprovalDecision` models and the `IApprovalService` used to request a
|
||||
* `ApprovalDecision` models and the `ISessionApprovalService` used to request a
|
||||
* decision, resolve it, and list pending approvals. Session-scoped — one
|
||||
* broker per session.
|
||||
*/
|
||||
|
|
@ -30,7 +30,7 @@ export interface ApprovalResponse {
|
|||
readonly selectedLabel?: string;
|
||||
}
|
||||
|
||||
export interface IApprovalService {
|
||||
export interface ISessionApprovalService {
|
||||
readonly _serviceBrand: undefined;
|
||||
request(req: ApprovalRequest): Promise<ApprovalResponse>;
|
||||
/**
|
||||
|
|
@ -43,5 +43,5 @@ export interface IApprovalService {
|
|||
listPending(): readonly ApprovalRequest[];
|
||||
}
|
||||
|
||||
export const IApprovalService: ServiceIdentifier<IApprovalService> =
|
||||
createDecorator<IApprovalService>('approvalService');
|
||||
export const ISessionApprovalService: ServiceIdentifier<ISessionApprovalService> =
|
||||
createDecorator<ISessionApprovalService>('sessionApprovalService');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* `approval` domain (L7) — `IApprovalService` implementation.
|
||||
* `approval` domain (L7) — `ISessionApprovalService` implementation.
|
||||
*
|
||||
* Typed facade over the `interaction` kernel for approval requests; owns no
|
||||
* pending state of its own (the kernel holds it). Bound at Session scope.
|
||||
|
|
@ -7,18 +7,18 @@
|
|||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { IInteractionService } from '#/interaction';
|
||||
import { ISessionInteractionService } from '#/interaction';
|
||||
|
||||
import {
|
||||
type ApprovalRequest,
|
||||
type ApprovalResponse,
|
||||
IApprovalService,
|
||||
ISessionApprovalService,
|
||||
} from './approval';
|
||||
|
||||
export class ApprovalService implements IApprovalService {
|
||||
export class SessionApprovalService implements ISessionApprovalService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(@IInteractionService private readonly interaction: IInteractionService) {}
|
||||
constructor(@ISessionInteractionService private readonly interaction: ISessionInteractionService) {}
|
||||
|
||||
request(req: ApprovalRequest): Promise<ApprovalResponse> {
|
||||
return this.interaction.request<ApprovalRequest, ApprovalResponse>({
|
||||
|
|
@ -55,4 +55,4 @@ function requestId(req: ApprovalRequest): string {
|
|||
return req.id ?? req.toolCallId ?? `${req.toolName}:${String(Date.now())}`;
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.Session, IApprovalService, ApprovalService, InstantiationType.Delayed, 'approval');
|
||||
registerScopedService(LifecycleScope.Session, ISessionApprovalService, SessionApprovalService, InstantiationType.Delayed, 'approval');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* `approval` domain barrel — re-exports the approval contract (`approval`) and
|
||||
* its scoped service (`approvalService`). Importing this barrel registers the
|
||||
* `IApprovalService` binding into the scope registry.
|
||||
* `ISessionApprovalService` binding into the scope registry.
|
||||
*/
|
||||
|
||||
export * from './approval';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* `auth` domain (cross-cutting) — core-scope OAuth + auth summary contracts.
|
||||
* `auth` domain (cross-cutting) — app-scope OAuth + auth summary contracts.
|
||||
*
|
||||
* Defines the public contracts of authentication: the `AuthStatus` model, the
|
||||
* `IOAuthService` used to drive device-code login / logout / flow inspection,
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
* OAuth provider's server-side model configuration, the `IOAuthToolkit`
|
||||
* device-code client that `IOAuthService` delegates the OAuth protocol to, and
|
||||
* the `IAuthSummaryService` used to summarize auth state and assert readiness.
|
||||
* Core-scoped — shared across the application.
|
||||
* App-scoped — shared across the application.
|
||||
*/
|
||||
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
* through `telemetry`, logs through `log`, and delegates the device-code
|
||||
* protocol, token storage, and token refresh to `IOAuthToolkit` (provided by
|
||||
* `OAuthToolkitService` over `@moonshot-ai/kimi-code-oauth`, which locates
|
||||
* token storage through `bootstrap`). Bound at Core scope.
|
||||
* token storage through `bootstrap`). Bound at App scope.
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
|
@ -616,6 +616,6 @@ class OAuthToolkitService extends KimiOAuthToolkit implements IOAuthToolkit {
|
|||
}
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.Core, IOAuthService, OAuthService, InstantiationType.Delayed, 'auth');
|
||||
registerScopedService(LifecycleScope.Core, IOAuthToolkit, OAuthToolkitService, InstantiationType.Delayed, 'auth');
|
||||
registerScopedService(LifecycleScope.Core, IAuthSummaryService, AuthSummaryService, InstantiationType.Delayed, 'auth');
|
||||
registerScopedService(LifecycleScope.App, IOAuthService, OAuthService, InstantiationType.Delayed, 'auth');
|
||||
registerScopedService(LifecycleScope.App, IOAuthToolkit, OAuthToolkitService, InstantiationType.Delayed, 'auth');
|
||||
registerScopedService(LifecycleScope.App, IAuthSummaryService, AuthSummaryService, InstantiationType.Delayed, 'auth');
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* native v2 services (`IProviderService`, `IConfigService`, `IOAuthService`).
|
||||
* The native `IAuthSummaryService` keeps serving `/api/v2` (`auth:summarize` /
|
||||
* `auth:ensureReady`) and is left untouched; this adapter exists only so v1
|
||||
* clients keep working against server-v2. Bound at Core scope — it is a
|
||||
* clients keep working against server-v2. Bound at App scope — it is a
|
||||
* stateless projector over the global provider / model / credential state.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* `authLegacy` domain — `IAuthLegacyService` implementation.
|
||||
*
|
||||
* Stateless Core-scope projector: reads the configured providers through
|
||||
* Stateless App-scope projector: reads the configured providers through
|
||||
* `provider`, the global default-model selection through `config`, and the
|
||||
* managed OAuth provider's cached-token state through `auth`, then assembles
|
||||
* the v1 `AuthSummary`. The computation mirrors v1's `AuthSummaryService.get()`
|
||||
|
|
@ -77,7 +77,7 @@ function nonEmpty(value: string | undefined): string | null {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IAuthLegacyService,
|
||||
AuthLegacyService,
|
||||
InstantiationType.Delayed,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export interface RegisterBackgroundTaskOptions {
|
|||
|
||||
export type ForegroundTaskReleaseReason = 'detached' | 'terminal';
|
||||
|
||||
export interface IBackgroundService {
|
||||
export interface IAgentBackgroundService {
|
||||
readonly _serviceBrand: undefined;
|
||||
registerTask(task: BackgroundTask, options?: RegisterBackgroundTaskOptions): string;
|
||||
getTask(taskId: string): BackgroundTaskInfo | undefined;
|
||||
|
|
@ -66,5 +66,5 @@ export interface IBackgroundService {
|
|||
): Promise<ForegroundTaskReleaseReason | undefined>;
|
||||
}
|
||||
|
||||
export const IBackgroundService =
|
||||
createDecorator<IBackgroundService>('agentBackgroundService');
|
||||
export const IAgentBackgroundService =
|
||||
createDecorator<IAgentBackgroundService>('agentBackgroundService');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* `background` domain (L5) — `BackgroundService` implementation.
|
||||
* `background` domain (L5) — `AgentBackgroundService` implementation.
|
||||
*
|
||||
* Owns the agent's registry of running and restored background tasks:
|
||||
* registers and drives tasks to completion, retains a bounded output ring,
|
||||
|
|
@ -25,19 +25,19 @@ import {
|
|||
type BackgroundTaskSettlement,
|
||||
} from './task';
|
||||
|
||||
import { IContextMemory } from '#/contextMemory';
|
||||
import { IAgentContextMemoryService } from '#/contextMemory';
|
||||
import { IConfigRegistry, IConfigService } from '#/config';
|
||||
import { IEventSink } from '../eventSink';
|
||||
import { IExternalHooksService } from '#/externalHooks';
|
||||
import { IPromptService } from '#/prompt';
|
||||
import { IAgentEventSinkService } from '../eventSink';
|
||||
import { IAgentExternalHooksService } from '#/externalHooks';
|
||||
import { IAgentPromptService } from '#/prompt';
|
||||
import { ISessionContext } from '#/session-context';
|
||||
import { IAtomicDocumentStore, IStorageService } from '#/storage';
|
||||
import { ITelemetryService } from '#/telemetry';
|
||||
import { IToolRegistry } from '#/toolRegistry';
|
||||
import { IAgentToolRegistryService } from '#/toolRegistry';
|
||||
import type { WireRecord } from '#/wireRecord';
|
||||
import { IWireRecord } from '#/wireRecord';
|
||||
import { IAgentWireRecordService } from '#/wireRecord';
|
||||
import {
|
||||
IBackgroundService,
|
||||
IAgentBackgroundService,
|
||||
type BackgroundLoadOptions,
|
||||
type BackgroundTask,
|
||||
type BackgroundTaskInfo,
|
||||
|
|
@ -123,7 +123,7 @@ export function isBackgroundTaskTerminal(status: BackgroundTaskStatus): boolean
|
|||
return TERMINAL_STATUSES.has(status);
|
||||
}
|
||||
|
||||
export class BackgroundService extends Disposable implements IBackgroundService {
|
||||
export class AgentBackgroundService extends Disposable implements IAgentBackgroundService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly tasks = new Map<string, ManagedTask>();
|
||||
|
|
@ -133,13 +133,13 @@ export class BackgroundService extends Disposable implements IBackgroundService
|
|||
private readonly persistence: BackgroundTaskPersistence;
|
||||
|
||||
constructor(
|
||||
@IEventSink private readonly events: IEventSink,
|
||||
@IWireRecord private readonly wireRecord: IWireRecord,
|
||||
@IAgentEventSinkService private readonly events: IAgentEventSinkService,
|
||||
@IAgentWireRecordService private readonly wireRecord: IAgentWireRecordService,
|
||||
@ITelemetryService private readonly telemetry: ITelemetryService,
|
||||
@IPromptService private readonly prompt: IPromptService,
|
||||
@IExternalHooksService private readonly externalHooks: IExternalHooksService,
|
||||
@IContextMemory private readonly context: IContextMemory,
|
||||
@IToolRegistry toolRegistry: IToolRegistry,
|
||||
@IAgentPromptService private readonly prompt: IAgentPromptService,
|
||||
@IAgentExternalHooksService private readonly externalHooks: IAgentExternalHooksService,
|
||||
@IAgentContextMemoryService private readonly context: IAgentContextMemoryService,
|
||||
@IAgentToolRegistryService toolRegistry: IAgentToolRegistryService,
|
||||
@IConfigRegistry configRegistry: IConfigRegistry,
|
||||
@IConfigService private readonly config: IConfigService,
|
||||
@IAtomicDocumentStore atomicDocs: IAtomicDocumentStore,
|
||||
|
|
@ -944,12 +944,12 @@ function errorMessage(error: unknown): string {
|
|||
return String(error);
|
||||
}
|
||||
|
||||
export { BackgroundService as Background };
|
||||
export { AgentBackgroundService as Background };
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IBackgroundService,
|
||||
BackgroundService,
|
||||
IAgentBackgroundService,
|
||||
AgentBackgroundService,
|
||||
InstantiationType.Delayed,
|
||||
'background',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* `background` domain (L5) — `background` config-section schema.
|
||||
*
|
||||
* Owns the `[background]` configuration section (background-task limits and
|
||||
* lifecycle tuning). Registered into `IConfigRegistry` by `BackgroundService`
|
||||
* lifecycle tuning). Registered into `IConfigRegistry` by `AgentBackgroundService`
|
||||
* on construction, so the `config` domain never imports this domain's types.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* `background` domain barrel — re-exports the background contract
|
||||
* (`background`) and its scoped service (`backgroundService`). Importing this
|
||||
* barrel registers the `IBackgroundService` binding into the scope registry.
|
||||
* barrel registers the `IAgentBackgroundService` binding into the scope registry.
|
||||
*/
|
||||
|
||||
export * from './background';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* `background` domain (L5) — `BackgroundTaskPersistence`, the per-session
|
||||
* persistence helper behind `BackgroundService`.
|
||||
* persistence helper behind `AgentBackgroundService`.
|
||||
*
|
||||
* Persists task state (`<taskId>.json`) and raw task output (`output.log`)
|
||||
* through the `storage` access-pattern stores (`IAtomicDocumentStore` for
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
* `{prefix}-{8 hex}` shape before use as path segments (path-traversal and
|
||||
* legacy `bg_<hex>` guard), and legacy snake_case records are normalized to
|
||||
* the current shape on read. Not scope-bound; constructed by
|
||||
* `BackgroundService`.
|
||||
* `AgentBackgroundService`.
|
||||
*/
|
||||
|
||||
import { join } from 'pathe';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { toInputJsonSchema } from '#/_base/tools/support/input-schema';
|
|||
import { matchesGlobRuleSubject } from '#/_base/tools/support/rule-match';
|
||||
import type { BuiltinTool, ToolExecution } from '#/tool';
|
||||
|
||||
import type { BackgroundTaskInfo, IBackgroundService } from '../background';
|
||||
import type { BackgroundTaskInfo, IAgentBackgroundService } from '../background';
|
||||
import { formatPlainObject } from './format';
|
||||
import TASK_LIST_DESCRIPTION from './task-list.md?raw';
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ export class TaskListTool implements BuiltinTool<TaskListInput> {
|
|||
readonly description = TASK_LIST_DESCRIPTION;
|
||||
readonly parameters: Record<string, unknown> = toInputJsonSchema(TaskListInputSchema);
|
||||
|
||||
constructor(private readonly background: IBackgroundService) {}
|
||||
constructor(private readonly background: IAgentBackgroundService) {}
|
||||
|
||||
resolveExecution(args: TaskListInput): ToolExecution {
|
||||
const listScope = (args.active_only ?? true) ? 'active' : 'all';
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { BuiltinTool, ExecutableToolResult, ToolExecution } from '#/tool';
|
|||
import type {
|
||||
BackgroundTaskInfo,
|
||||
BackgroundTaskOutputSnapshot,
|
||||
IBackgroundService,
|
||||
IAgentBackgroundService,
|
||||
} from '../background';
|
||||
import { type BackgroundTaskStatus, TERMINAL_STATUSES } from '../task';
|
||||
import { formatPlainObject } from './format';
|
||||
|
|
@ -98,7 +98,7 @@ export class TaskOutputTool implements BuiltinTool<TaskOutputInput> {
|
|||
readonly description: string = TASK_OUTPUT_DESCRIPTION;
|
||||
readonly parameters: Record<string, unknown> = toInputJsonSchema(TaskOutputInputSchema);
|
||||
|
||||
constructor(private readonly background: IBackgroundService) {}
|
||||
constructor(private readonly background: IAgentBackgroundService) {}
|
||||
|
||||
resolveExecution(args: TaskOutputInput): ToolExecution {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { toInputJsonSchema } from '#/_base/tools/support/input-schema';
|
|||
import { matchesGlobRuleSubject } from '#/_base/tools/support/rule-match';
|
||||
import type { BuiltinTool, ToolExecution } from '#/tool';
|
||||
|
||||
import type { IBackgroundService } from '../background';
|
||||
import type { IAgentBackgroundService } from '../background';
|
||||
import { TERMINAL_STATUSES } from '../task';
|
||||
import TASK_STOP_DESCRIPTION from './task-stop.md?raw';
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ export class TaskStopTool implements BuiltinTool<TaskStopInput> {
|
|||
readonly description = TASK_STOP_DESCRIPTION;
|
||||
readonly parameters: Record<string, unknown> = toInputJsonSchema(TaskStopInputSchema);
|
||||
|
||||
constructor(private readonly background: IBackgroundService) {}
|
||||
constructor(private readonly background: IAgentBackgroundService) {}
|
||||
|
||||
resolveExecution(args: TaskStopInput): ToolExecution {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import { createDecorator } from "#/_base/di";
|
|||
export const BLOBREF_PROTOCOL = 'blobref:';
|
||||
export const MISSING_MEDIA_PLACEHOLDER = '[media missing]';
|
||||
|
||||
export interface IBlobStoreService {
|
||||
export interface IAgentBlobStoreService {
|
||||
readonly _serviceBrand: undefined;
|
||||
offloadParts(parts: readonly ContentPart[]): Promise<readonly ContentPart[]>;
|
||||
rehydrateParts(parts: readonly ContentPart[]): Promise<readonly ContentPart[]>;
|
||||
isBlobRef(url: string): boolean;
|
||||
}
|
||||
|
||||
export const IBlobStoreService = createDecorator<IBlobStoreService>(
|
||||
export const IAgentBlobStoreService = createDecorator<IAgentBlobStoreService>(
|
||||
'agentBlobStoreService',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { IBlobStorage, type IStorageService } from '#/storage';
|
|||
|
||||
import {
|
||||
BLOBREF_PROTOCOL,
|
||||
IBlobStoreService,
|
||||
IAgentBlobStoreService,
|
||||
MISSING_MEDIA_PLACEHOLDER,
|
||||
} from './blobStore';
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ const DEFAULT_MAX_CACHE_SIZE = 50 * 1024 * 1024;
|
|||
const DEFAULT_STORAGE_SCOPE = 'blobs';
|
||||
const DATA_URI_HEADER_RE = /^data:([^;]+);base64,/;
|
||||
|
||||
export class BlobStoreService implements IBlobStoreService {
|
||||
export class AgentBlobStoreService implements IAgentBlobStoreService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly storageScope = DEFAULT_STORAGE_SCOPE;
|
||||
|
|
@ -184,8 +184,8 @@ function asMediaContainer(value: unknown): { url: unknown } | undefined {
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IBlobStoreService,
|
||||
BlobStoreService,
|
||||
IAgentBlobStoreService,
|
||||
AgentBlobStoreService,
|
||||
InstantiationType.Delayed,
|
||||
'blobStore',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
* layout (`homeDir`, `configPath`, …). `resolveBootstrapOptions` is the single
|
||||
* place that reads `process.env` / `os.homedir()` / invocation input to resolve
|
||||
* the snapshot; everything downstream reads from `IBootstrapService` instead of
|
||||
* touching `process` directly. Bound at Core scope. Also seeds the Core storage
|
||||
* touching `process` directly. Bound at App scope. Also seeds the App storage
|
||||
* roles (`IStorageService`, `IAppendLogStorage`, `IAtomicDocumentStorage`,
|
||||
* `IBlobStorage`) each with its own `FileStorageService` rooted at `homeDir`
|
||||
* (via per-token `SyncDescriptor`s), so the byte layer (and every Store above
|
||||
|
|
@ -23,7 +23,7 @@ import type { Environment } from '#/kaos';
|
|||
|
||||
import { SyncDescriptor } from '#/_base/di/descriptors';
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
import { createCoreScope, type Scope, type ScopeSeed } from '#/_base/di/scope';
|
||||
import { createAppScope, type Scope, type ScopeSeed } from '#/_base/di/scope';
|
||||
import {
|
||||
FileStorageService,
|
||||
IAppendLogStorage,
|
||||
|
|
@ -101,15 +101,15 @@ export function bootstrapSeed(input: BootstrapInput = {}): ScopeSeed {
|
|||
}
|
||||
|
||||
export interface BootstrapResult {
|
||||
readonly core: Scope;
|
||||
readonly app: Scope;
|
||||
}
|
||||
|
||||
export function bootstrap(input: BootstrapInput = {}, extraSeeds: ScopeSeed = []): BootstrapResult {
|
||||
const options = resolveBootstrapOptions(input);
|
||||
const core = createCoreScope({
|
||||
const app = createAppScope({
|
||||
extra: [...bootstrapSeed(input), ...storageSeed(options), ...skillSeed(), ...extraSeeds],
|
||||
});
|
||||
return { core };
|
||||
return { app };
|
||||
}
|
||||
|
||||
function storageSeed(options: IBootstrapOptions): ScopeSeed {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Holds the resolved startup snapshot from the seeded `IBootstrapOptions` and
|
||||
* exposes the host facts and app path layout; `detect()` probes the host through
|
||||
* `kaos` on demand. Bound at Core scope.
|
||||
* `kaos` on demand. Bound at App scope.
|
||||
*/
|
||||
|
||||
import { join } from 'pathe';
|
||||
|
|
@ -57,4 +57,4 @@ export class BootstrapService implements IBootstrapService {
|
|||
}
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.Core, IBootstrapService, BootstrapService, InstantiationType.Eager, 'bootstrap');
|
||||
registerScopedService(LifecycleScope.App, IBootstrapService, BootstrapService, InstantiationType.Eager, 'bootstrap');
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Builds the protocol adapter (`ChatProvider`) that speaks a given provider
|
||||
* `type`. A provider is a configured endpoint (baseUrl / apiKey / model); the
|
||||
* factory is the adapter that speaks its wire protocol — different providers
|
||||
* may share one adapter. Bound at Core scope.
|
||||
* may share one adapter. Bound at App scope.
|
||||
*/
|
||||
|
||||
import type { ChatProvider, ProviderConfig, ProviderType } from '@moonshot-ai/kosong';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Dispatches to the adapter registered for a provider `type`, falling back to
|
||||
* the built-in adapters from the `@moonshot-ai/kosong` package's
|
||||
* `createProvider`. Owns no configuration and no business dependencies. Bound
|
||||
* at Core scope.
|
||||
* at App scope.
|
||||
*/
|
||||
|
||||
import type { ChatProvider, ProviderConfig, ProviderType } from '@moonshot-ai/kosong';
|
||||
|
|
@ -30,7 +30,7 @@ export class ChatProviderFactory implements IChatProviderFactory {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IChatProviderFactory,
|
||||
ChatProviderFactory,
|
||||
InstantiationType.Delayed,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* `config` domain (L2) — configuration registry and layered global config service.
|
||||
*
|
||||
* Defines the config service identifiers and section models: the
|
||||
* `IConfigRegistry` for section schemas, and the Core-scoped `IConfigService`
|
||||
* `IConfigRegistry` for section schemas, and the App-scoped `IConfigService`
|
||||
* that resolves a value by precedence across layers (defaults → user config →
|
||||
* per-run memory overrides) and writes through a `ConfigTarget`. Owners react
|
||||
* to edits through two change events — `onDidChange` (a domain was touched) and
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
* `bootstrap`, persists the TOML document through the `storage` TOML
|
||||
* atomic-document store (reloading when the document changes on disk), and logs
|
||||
* through `log`. Late section / overlay registration re-validates the
|
||||
* already-loaded raw value and re-runs overlays. Bound at Core scope.
|
||||
* already-loaded raw value and re-runs overlays. Bound at App scope.
|
||||
*/
|
||||
|
||||
import { basename } from 'pathe';
|
||||
|
|
@ -480,14 +480,14 @@ export class ConfigService extends Disposable implements IConfigService {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IConfigRegistry,
|
||||
ConfigRegistry,
|
||||
InstantiationType.Delayed,
|
||||
'config',
|
||||
);
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IConfigService,
|
||||
ConfigService,
|
||||
InstantiationType.Delayed,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export type ContextInjectionProvider = (
|
|||
context: ContextInjectionContext,
|
||||
) => string | undefined | Promise<string | undefined>;
|
||||
|
||||
export interface IContextInjector {
|
||||
export interface IAgentContextInjectorService {
|
||||
readonly _serviceBrand: undefined;
|
||||
register(
|
||||
variant: string,
|
||||
|
|
@ -22,6 +22,6 @@ export interface IContextInjector {
|
|||
): IDisposable;
|
||||
}
|
||||
|
||||
export const IContextInjector = createDecorator<IContextInjector>(
|
||||
'contextInjectorService',
|
||||
export const IAgentContextInjectorService = createDecorator<IAgentContextInjectorService>(
|
||||
'agentContextInjectorService',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import {
|
|||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
|
||||
import { IContextMemory } from '../contextMemory';
|
||||
import { ISystemReminderService } from '../systemReminder';
|
||||
import { ITurnService } from '../turn';
|
||||
import { IAgentContextMemoryService } from '../contextMemory';
|
||||
import { IAgentSystemReminderService } from '../systemReminder';
|
||||
import { IAgentTurnService } from '../turn';
|
||||
import type { ContextMessage } from '#/contextMemory';
|
||||
import {
|
||||
IContextInjector,
|
||||
IAgentContextInjectorService,
|
||||
type ContextInjectionOptions,
|
||||
type ContextInjectionProvider,
|
||||
} from './contextInjector';
|
||||
|
|
@ -24,15 +24,15 @@ interface ContextInjectionEntry {
|
|||
turnConsumed: boolean;
|
||||
}
|
||||
|
||||
export class ContextInjectorService extends Disposable implements IContextInjector {
|
||||
export class AgentContextInjectorService extends Disposable implements IAgentContextInjectorService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private readonly entries = new Set<ContextInjectionEntry>();
|
||||
private readonly selfInsertedMessages = new WeakMap<ContextMessage, ContextInjectionEntry>();
|
||||
|
||||
constructor(
|
||||
@IContextMemory private readonly context: IContextMemory,
|
||||
@ITurnService turnService: ITurnService,
|
||||
@ISystemReminderService private readonly reminders: ISystemReminderService,
|
||||
@IAgentContextMemoryService private readonly context: IAgentContextMemoryService,
|
||||
@IAgentTurnService turnService: IAgentTurnService,
|
||||
@IAgentSystemReminderService private readonly reminders: IAgentSystemReminderService,
|
||||
) {
|
||||
super();
|
||||
this._register(
|
||||
|
|
@ -186,8 +186,8 @@ function findLastInjection(
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IContextInjector,
|
||||
ContextInjectorService,
|
||||
IAgentContextInjectorService,
|
||||
AgentContextInjectorService,
|
||||
InstantiationType.Delayed,
|
||||
'contextInjector',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { createDecorator } from "#/_base/di";
|
|||
import type { Hooks } from '#/hooks';
|
||||
import type { ContextMessage } from './types';
|
||||
|
||||
export interface IContextMemory {
|
||||
export interface IAgentContextMemoryService {
|
||||
readonly _serviceBrand: undefined;
|
||||
get(): readonly ContextMessage[];
|
||||
splice(
|
||||
|
|
@ -23,4 +23,4 @@ export interface IContextMemory {
|
|||
}>;
|
||||
}
|
||||
|
||||
export const IContextMemory = createDecorator<IContextMemory>('agentContextMemoryService');
|
||||
export const IAgentContextMemoryService = createDecorator<IAgentContextMemoryService>('agentContextMemoryService');
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import {
|
|||
Disposable,
|
||||
} from "#/_base/di";
|
||||
import { OrderedHookSlot } from '#/hooks';
|
||||
import { IReplayBuilderService } from '#/replayBuilder';
|
||||
import { IWireRecord, type WireRecord } from '#/wireRecord';
|
||||
import { IContextMemory } from './contextMemory';
|
||||
import { IAgentReplayBuilderService } from '#/replayBuilder';
|
||||
import { IAgentWireRecordService, type WireRecord } from '#/wireRecord';
|
||||
import { IAgentContextMemoryService } from './contextMemory';
|
||||
import { ensureMessageId } from './messageId';
|
||||
import type { ContextMessage } from './types';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
|
|
@ -21,7 +21,7 @@ declare module '#/wireRecord' {
|
|||
}
|
||||
}
|
||||
|
||||
export class ContextMemoryService extends Disposable implements IContextMemory {
|
||||
export class AgentContextMemoryService extends Disposable implements IAgentContextMemoryService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private readonly history: ContextMessage[] = [];
|
||||
|
||||
|
|
@ -35,8 +35,8 @@ export class ContextMemoryService extends Disposable implements IContextMemory {
|
|||
};
|
||||
|
||||
constructor(
|
||||
@IWireRecord private readonly wireRecord: IWireRecord,
|
||||
@IReplayBuilderService private readonly replayBuilder: IReplayBuilderService,
|
||||
@IAgentWireRecordService private readonly wireRecord: IAgentWireRecordService,
|
||||
@IAgentReplayBuilderService private readonly replayBuilder: IAgentReplayBuilderService,
|
||||
) {
|
||||
super();
|
||||
this._register(
|
||||
|
|
@ -104,8 +104,8 @@ export class ContextMemoryService extends Disposable implements IContextMemory {
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IContextMemory,
|
||||
ContextMemoryService,
|
||||
IAgentContextMemoryService,
|
||||
AgentContextMemoryService,
|
||||
InstantiationType.Delayed,
|
||||
'contextMemory',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* `contextMemory` message id helpers.
|
||||
*
|
||||
* Every `ContextMessage` gets a stable local id (`msg_<ulid>`) when it enters
|
||||
* `IContextMemory` — see `ContextMemoryService.splice`. The id is persisted in
|
||||
* `IAgentContextMemoryService` — see `AgentContextMemoryService.splice`. The id is persisted in
|
||||
* the `context.splice` wire record, so it is stable across restarts. It is the
|
||||
* identity used by `Turn.promptMessageId`, snapshot `current_prompt_id`, and
|
||||
* message lookup. Provider-assigned ids live on the separate
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import type { Message } from '@moonshot-ai/kosong';
|
|||
|
||||
import type { ContextMessage } from '#/contextMemory';
|
||||
|
||||
export interface IContextProjector {
|
||||
export interface IAgentContextProjectorService {
|
||||
readonly _serviceBrand: undefined;
|
||||
project(messages: readonly ContextMessage[]): readonly Message[];
|
||||
}
|
||||
|
||||
export const IContextProjector = createDecorator<IContextProjector>(
|
||||
export const IAgentContextProjectorService = createDecorator<IAgentContextProjectorService>(
|
||||
'agentContextProjectorService',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { InstantiationType } from '#/_base/di/extensions';
|
|||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import type { ContextMessage } from '#/contextMemory/types';
|
||||
import { ErrorCodes, KimiError } from '#/errors';
|
||||
import { IMicroCompactionService } from '#/microCompaction';
|
||||
import { IAgentMicroCompactionService } from '#/microCompaction';
|
||||
import type { ContentPart, Message, TextPart } from '@moonshot-ai/kosong';
|
||||
import { IContextProjector } from './contextProjector';
|
||||
import { IAgentContextProjectorService } from './contextProjector';
|
||||
|
||||
export class ContextProjectorService implements IContextProjector {
|
||||
export class AgentContextProjectorService implements IAgentContextProjectorService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiation: IInstantiationService,
|
||||
|
|
@ -19,9 +19,9 @@ export class ContextProjectorService implements IContextProjector {
|
|||
return project(this.microCompaction().compact(messages));
|
||||
}
|
||||
|
||||
private microCompaction(): IMicroCompactionService {
|
||||
private microCompaction(): IAgentMicroCompactionService {
|
||||
return this.instantiation.invokeFunction((accessor) =>
|
||||
accessor.get(IMicroCompactionService),
|
||||
accessor.get(IAgentMicroCompactionService),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -226,8 +226,8 @@ function isInterruptedToolResult(message: Message): boolean {
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IContextProjector,
|
||||
ContextProjectorService,
|
||||
IAgentContextProjectorService,
|
||||
AgentContextProjectorService,
|
||||
InstantiationType.Delayed,
|
||||
'contextProjector',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ export interface ContextSizeStatus {
|
|||
readonly contextTokensWithPending: number;
|
||||
}
|
||||
|
||||
export interface IContextSizeService {
|
||||
export interface IAgentContextSizeService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
getStatus(): ContextSizeStatus;
|
||||
measured(length: number, tokens: number): void;
|
||||
}
|
||||
|
||||
export const IContextSizeService =
|
||||
createDecorator<IContextSizeService>('agentContextSizeService');
|
||||
export const IAgentContextSizeService =
|
||||
createDecorator<IAgentContextSizeService>('agentContextSizeService');
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import {
|
|||
estimateTokensForMessage,
|
||||
} from "#/_base/utils/tokens";
|
||||
import type { ContextMessage } from '#/contextMemory';
|
||||
import { IContextMemory } from '#/contextMemory';
|
||||
import { IEventSink } from '../eventSink';
|
||||
import { IWireRecord, type WireRecord } from '#/wireRecord';
|
||||
import { IAgentContextMemoryService } from '#/contextMemory';
|
||||
import { IAgentEventSinkService } from '../eventSink';
|
||||
import { IAgentWireRecordService, type WireRecord } from '#/wireRecord';
|
||||
import {
|
||||
IContextSizeService,
|
||||
IAgentContextSizeService,
|
||||
type ContextSizeStatus,
|
||||
} from './contextSize';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
|
|
@ -24,9 +24,9 @@ declare module '#/wireRecord' {
|
|||
}
|
||||
}
|
||||
|
||||
export class ContextSizeService
|
||||
export class AgentContextSizeService
|
||||
extends Disposable
|
||||
implements IContextSizeService
|
||||
implements IAgentContextSizeService
|
||||
{
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
|
|
@ -38,9 +38,9 @@ export class ContextSizeService
|
|||
};
|
||||
|
||||
constructor(
|
||||
@IContextMemory private readonly context: IContextMemory,
|
||||
@IEventSink private readonly events: IEventSink,
|
||||
@IWireRecord private readonly wireRecord: IWireRecord,
|
||||
@IAgentContextMemoryService private readonly context: IAgentContextMemoryService,
|
||||
@IAgentEventSinkService private readonly events: IAgentEventSinkService,
|
||||
@IAgentWireRecordService private readonly wireRecord: IAgentWireRecordService,
|
||||
) {
|
||||
super();
|
||||
this._register(
|
||||
|
|
@ -157,8 +157,8 @@ function sum(values: readonly number[]): number {
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IContextSizeService,
|
||||
ContextSizeService,
|
||||
IAgentContextSizeService,
|
||||
AgentContextSizeService,
|
||||
InstantiationType.Delayed,
|
||||
'contextSize',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export interface CronFireOptions {
|
|||
readonly firedAt?: number;
|
||||
}
|
||||
|
||||
export interface ICronService extends CronToolManager {
|
||||
export interface IAgentCronService extends CronToolManager {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly isEnabled: boolean;
|
||||
getTask(id: string): CronTask | undefined;
|
||||
|
|
@ -46,4 +46,4 @@ export interface ICronService extends CronToolManager {
|
|||
flushPersist(): Promise<void>;
|
||||
}
|
||||
|
||||
export const ICronService = createDecorator<ICronService>('agentCronService');
|
||||
export const IAgentCronService = createDecorator<IAgentCronService>('agentCronService');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* `cron` domain (L5) — `CronService` implementation.
|
||||
* `cron` domain (L5) — `AgentCronService` implementation.
|
||||
*
|
||||
* Owns the agent's cron task set: schedules and fires due tasks (steering
|
||||
* the agent through `prompt`), persists task records through the `cron`
|
||||
|
|
@ -17,17 +17,17 @@ import {
|
|||
toDisposable,
|
||||
} from "#/_base/di";
|
||||
import type { ContextMessage } from '#/contextMemory';
|
||||
import { IEventSink } from '../eventSink';
|
||||
import { IAgentEventSinkService } from '../eventSink';
|
||||
import { IConfigRegistry, IConfigService } from '#/config';
|
||||
import { IPromptService } from '#/prompt';
|
||||
import { IAgentPromptService } from '#/prompt';
|
||||
import { IAtomicDocumentStore } from '#/storage';
|
||||
import { ITelemetryService } from '#/telemetry';
|
||||
import { IToolRegistry } from '#/toolRegistry';
|
||||
import { IAgentToolRegistryService } from '#/toolRegistry';
|
||||
import type { Turn } from '#/turn';
|
||||
import { ITurnService } from '#/turn';
|
||||
import { IWireRecord } from '#/wireRecord';
|
||||
import { IAgentTurnService } from '#/turn';
|
||||
import { IAgentWireRecordService } from '#/wireRecord';
|
||||
import {
|
||||
ICronService,
|
||||
IAgentCronService,
|
||||
type CronFireOptions,
|
||||
type CronLoadOptions,
|
||||
type CronOptions,
|
||||
|
|
@ -82,9 +82,9 @@ declare module '#/wireRecord' {
|
|||
|
||||
const STALE_THRESHOLD_MS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
export class CronService
|
||||
export class AgentCronService
|
||||
extends Disposable
|
||||
implements ICronService, CronToolManager {
|
||||
implements IAgentCronService, CronToolManager {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
readonly store = new SessionCronStore();
|
||||
|
|
@ -100,12 +100,12 @@ export class CronService
|
|||
|
||||
constructor(
|
||||
options: CronOptions = {},
|
||||
@IPromptService private readonly prompt: IPromptService,
|
||||
@IEventSink private readonly events: IEventSink,
|
||||
@IWireRecord private readonly wireRecord: IWireRecord,
|
||||
@ITurnService private readonly turnService: ITurnService,
|
||||
@IAgentPromptService private readonly prompt: IAgentPromptService,
|
||||
@IAgentEventSinkService private readonly events: IAgentEventSinkService,
|
||||
@IAgentWireRecordService private readonly wireRecord: IAgentWireRecordService,
|
||||
@IAgentTurnService private readonly turnService: IAgentTurnService,
|
||||
@ITelemetryService private readonly telemetry: ITelemetryService,
|
||||
@IToolRegistry private readonly toolRegistry: IToolRegistry,
|
||||
@IAgentToolRegistryService private readonly toolRegistry: IAgentToolRegistryService,
|
||||
@IConfigRegistry configRegistry: IConfigRegistry,
|
||||
@IConfigService private readonly config: IConfigService,
|
||||
@IAtomicDocumentStore private readonly atomicDocs: IAtomicDocumentStore,
|
||||
|
|
@ -442,8 +442,8 @@ export class CronService
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
ICronService,
|
||||
CronService,
|
||||
IAgentCronService,
|
||||
AgentCronService,
|
||||
InstantiationType.Delayed,
|
||||
'cron',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* `cron` domain barrel — re-exports the cron contract (`cron`) and its scoped
|
||||
* service (`cronService`). Importing this barrel registers the `ICronService`
|
||||
* service (`cronService`). Importing this barrel registers the `IAgentCronService`
|
||||
* and `ICronFireCoordinator` bindings into the scope registry.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* SessionCronStore — in-memory cron task store for a single CLI session.
|
||||
*
|
||||
* The store itself is purely in-memory; cross-restart persistence is
|
||||
* layered on top by `CronService.addTask` / `removeTasks`, which
|
||||
* layered on top by `AgentCronService.addTask` / `removeTasks`, which
|
||||
* mirror every mutation to `<sessionDir>/cron/<id>.json`. On resume
|
||||
* the service calls {@link adopt} to put each persisted task back into
|
||||
* the store with its original id and `createdAt` preserved.
|
||||
|
|
@ -68,7 +68,7 @@ export class SessionCronStore {
|
|||
|
||||
/**
|
||||
* Insert a previously-persisted task verbatim — id and createdAt
|
||||
* stay as they are on disk. Used by `CronService.loadFromDisk()` to
|
||||
* stay as they are on disk. Used by `AgentCronService.loadFromDisk()` to
|
||||
* rehydrate the store on resume. Unlike {@link add}, this does NOT
|
||||
* generate a new id; the caller is responsible for ensuring the id
|
||||
* matches the expected shape (the persistence layer's regex /
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Defines `IEventService`, a minimal type-tagged event bus used by business
|
||||
* domains to broadcast facts (for example session lifecycle changes) to an
|
||||
* unknown set of consumers. Bound at Core scope; a single global instance.
|
||||
* unknown set of consumers. Bound at App scope; a single global instance.
|
||||
*/
|
||||
|
||||
import { createDecorator, type IDisposable, type ServiceIdentifier } from '#/_base/di';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* `event` domain (L0) — `IEventService` implementation.
|
||||
*
|
||||
* Delivers published events to subscribers through the `_base/event` `Emitter`
|
||||
* primitive. Bound at Core scope.
|
||||
* primitive. Bound at App scope.
|
||||
*/
|
||||
|
||||
import { Disposable, type IDisposable } from '#/_base/di';
|
||||
|
|
@ -27,7 +27,7 @@ export class EventService extends Disposable implements IEventService {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IEventService,
|
||||
EventService,
|
||||
InstantiationType.Delayed,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import type { IDisposable } from "#/_base/di";
|
|||
import { createDecorator } from "#/_base/di";
|
||||
import type { AgentEvent } from '@moonshot-ai/protocol';
|
||||
|
||||
export interface IEventSink {
|
||||
export interface IAgentEventSinkService {
|
||||
readonly _serviceBrand: undefined;
|
||||
emit(event: AgentEvent): void;
|
||||
on(handler: (event: AgentEvent) => void): IDisposable;
|
||||
}
|
||||
|
||||
export const IEventSink = createDecorator<IEventSink>('agentEventSink');
|
||||
export const IAgentEventSinkService = createDecorator<IAgentEventSinkService>('agentEventSinkService');
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ import {
|
|||
} from "#/_base/di";
|
||||
import { Emitter } from "#/_base/event";
|
||||
|
||||
import { IWireRecord } from '#/wireRecord';
|
||||
import { IAgentWireRecordService } from '#/wireRecord';
|
||||
import type { AgentEvent } from '@moonshot-ai/protocol';
|
||||
import { IEventSink } from './eventSink';
|
||||
import { IAgentEventSinkService } from './eventSink';
|
||||
|
||||
export class EventSinkService extends Disposable implements IEventSink {
|
||||
export class AgentEventSinkService extends Disposable implements IAgentEventSinkService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private readonly onDidEmitEmitter = this._register(new Emitter<AgentEvent>());
|
||||
|
||||
constructor(@IWireRecord private readonly wireRecord: IWireRecord) {
|
||||
constructor(@IAgentWireRecordService private readonly wireRecord: IAgentWireRecordService) {
|
||||
super();
|
||||
}
|
||||
|
||||
|
|
@ -30,8 +30,8 @@ export class EventSinkService extends Disposable implements IEventSink {
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IEventSink,
|
||||
EventSinkService,
|
||||
IAgentEventSinkService,
|
||||
AgentEventSinkService,
|
||||
InstantiationType.Delayed,
|
||||
'eventSink',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* Owns the `[[hooks]]` configuration section (external hook definitions),
|
||||
* including the snake_case ↔ camelCase TOML transforms for each hook entry.
|
||||
* Registered into `IConfigRegistry` by `ExternalHooksService` on construction,
|
||||
* Registered into `IConfigRegistry` by `AgentExternalHooksService` on construction,
|
||||
* so the `config` domain never imports this domain's types.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export type PermissionResultHookPayload =
|
|||
readonly error: string;
|
||||
};
|
||||
|
||||
export interface IExternalHooksService {
|
||||
export interface IAgentExternalHooksService {
|
||||
readonly _serviceBrand: undefined;
|
||||
triggerPreToolUse(
|
||||
payload: {
|
||||
|
|
@ -98,5 +98,5 @@ export interface IExternalHooksService {
|
|||
}): void;
|
||||
}
|
||||
|
||||
export const IExternalHooksService =
|
||||
createDecorator<IExternalHooksService>('agentExternalHooksService');
|
||||
export const IAgentExternalHooksService =
|
||||
createDecorator<IAgentExternalHooksService>('agentExternalHooksService');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { toKimiErrorPayload } from "#/errors";
|
||||
import { IConfigRegistry } from '#/config';
|
||||
import {
|
||||
IExternalHooksService,
|
||||
IAgentExternalHooksService,
|
||||
type ExternalHooksServiceOptions,
|
||||
type NotificationHookPayload,
|
||||
type PermissionRequestHookPayload,
|
||||
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { isPlainRecord } from '#/_base/utils/canonical-args';
|
||||
import { IToolExecutor } from '#/toolExecutor';
|
||||
import { IAgentToolExecutorService } from '#/toolExecutor';
|
||||
|
||||
function fireAndForget(
|
||||
engine: ExternalHooksServiceOptions['hookEngine'],
|
||||
|
|
@ -32,12 +32,12 @@ function fireAndForget(
|
|||
void engine?.fireAndForgetTrigger(event, { matcherValue, signal, inputData });
|
||||
}
|
||||
|
||||
export class ExternalHooksService implements IExternalHooksService {
|
||||
export class AgentExternalHooksService implements IAgentExternalHooksService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
private readonly options: ExternalHooksServiceOptions = {},
|
||||
@IToolExecutor toolExecutor: IToolExecutor,
|
||||
@IAgentToolExecutorService toolExecutor: IAgentToolExecutorService,
|
||||
@IConfigRegistry configRegistry: IConfigRegistry,
|
||||
) {
|
||||
configRegistry.registerSection(HOOKS_SECTION, HooksConfigSchema, {
|
||||
|
|
@ -74,7 +74,7 @@ export class ExternalHooksService implements IExternalHooksService {
|
|||
}
|
||||
|
||||
async triggerPreToolUse(
|
||||
payload: Parameters<IExternalHooksService['triggerPreToolUse']>[0],
|
||||
payload: Parameters<IAgentExternalHooksService['triggerPreToolUse']>[0],
|
||||
signal: AbortSignal,
|
||||
): Promise<string | undefined> {
|
||||
signal.throwIfAborted();
|
||||
|
|
@ -92,7 +92,7 @@ export class ExternalHooksService implements IExternalHooksService {
|
|||
}
|
||||
|
||||
async triggerUserPromptSubmit(
|
||||
input: Parameters<IExternalHooksService['triggerUserPromptSubmit']>[0],
|
||||
input: Parameters<IAgentExternalHooksService['triggerUserPromptSubmit']>[0],
|
||||
signal: AbortSignal,
|
||||
): Promise<UserPromptHookDecision | undefined> {
|
||||
signal.throwIfAborted();
|
||||
|
|
@ -121,7 +121,7 @@ export class ExternalHooksService implements IExternalHooksService {
|
|||
}
|
||||
|
||||
async triggerPostToolUse(
|
||||
payload: Parameters<IExternalHooksService['triggerPostToolUse']>[0],
|
||||
payload: Parameters<IAgentExternalHooksService['triggerPostToolUse']>[0],
|
||||
signal: AbortSignal,
|
||||
): Promise<void> {
|
||||
const output = toolOutputText(payload.result.output);
|
||||
|
|
@ -176,14 +176,14 @@ export class ExternalHooksService implements IExternalHooksService {
|
|||
);
|
||||
}
|
||||
|
||||
triggerInterrupt(payload: Parameters<IExternalHooksService['triggerInterrupt']>[0]): void {
|
||||
triggerInterrupt(payload: Parameters<IAgentExternalHooksService['triggerInterrupt']>[0]): void {
|
||||
void this.options.hookEngine?.fireAndForgetTrigger('Interrupt', {
|
||||
inputData: payload,
|
||||
});
|
||||
}
|
||||
|
||||
async triggerPreCompact(
|
||||
payload: Parameters<IExternalHooksService['triggerPreCompact']>[0],
|
||||
payload: Parameters<IAgentExternalHooksService['triggerPreCompact']>[0],
|
||||
signal: AbortSignal,
|
||||
): Promise<void> {
|
||||
signal.throwIfAborted();
|
||||
|
|
@ -198,7 +198,7 @@ export class ExternalHooksService implements IExternalHooksService {
|
|||
signal.throwIfAborted();
|
||||
}
|
||||
|
||||
triggerPostCompact(payload: Parameters<IExternalHooksService['triggerPostCompact']>[0]): void {
|
||||
triggerPostCompact(payload: Parameters<IAgentExternalHooksService['triggerPostCompact']>[0]): void {
|
||||
void this.options.hookEngine?.fireAndForgetTrigger('PostCompact', {
|
||||
matcherValue: payload.trigger,
|
||||
inputData: {
|
||||
|
|
@ -221,7 +221,7 @@ export class ExternalHooksService implements IExternalHooksService {
|
|||
}
|
||||
|
||||
function toolOutputText(
|
||||
output: Parameters<IExternalHooksService['triggerPostToolUse']>[0]['result']['output'],
|
||||
output: Parameters<IAgentExternalHooksService['triggerPostToolUse']>[0]['result']['output'],
|
||||
): string {
|
||||
if (typeof output === 'string') return output;
|
||||
return output
|
||||
|
|
@ -257,8 +257,8 @@ function permissionResultInputData(payload: PermissionResultHookPayload): Record
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IExternalHooksService,
|
||||
ExternalHooksService,
|
||||
IAgentExternalHooksService,
|
||||
AgentExternalHooksService,
|
||||
InstantiationType.Delayed,
|
||||
'externalHooks',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
/**
|
||||
* `fileTools` domain (L4) — built-in file tool registration contract.
|
||||
*
|
||||
* `IFileToolsService` is a marker: its implementation registers the built-in
|
||||
* file tools (Read / Write / Edit / Grep / Glob) into the agent `IToolRegistry`
|
||||
* `IAgentFileToolsService` is a marker: its implementation registers the built-in
|
||||
* file tools (Read / Write / Edit / Grep / Glob) into the agent `IAgentToolRegistryService`
|
||||
* on construction. Bound at Agent scope.
|
||||
*/
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
||||
export interface IFileToolsService {
|
||||
export interface IAgentFileToolsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
}
|
||||
|
||||
export const IFileToolsService: ServiceIdentifier<IFileToolsService> =
|
||||
createDecorator<IFileToolsService>('fileToolsService');
|
||||
export const IAgentFileToolsService: ServiceIdentifier<IAgentFileToolsService> =
|
||||
createDecorator<IAgentFileToolsService>('agentFileToolsService');
|
||||
|
|
|
|||
|
|
@ -1,36 +1,36 @@
|
|||
/**
|
||||
* `fileTools` domain (L4) — `IFileToolsService` implementation.
|
||||
* `fileTools` domain (L4) — `IAgentFileToolsService` implementation.
|
||||
*
|
||||
* Registers the built-in file tools (Read / Write / Edit / Grep / Glob) into
|
||||
* the agent `IToolRegistry` on construction, wiring each to the session
|
||||
* `IAgentFileSystem` (file IO), `IFsService` (workspace search/grep), `IKaos`
|
||||
* the agent `IAgentToolRegistryService` on construction, wiring each to the session
|
||||
* `ISessionAgentFileSystem` (file IO), `ISessionFsService` (workspace search/grep), `IKaos`
|
||||
* (path semantics) and the session workspace. Bound at Agent scope.
|
||||
*/
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import type { WorkspaceConfig } from '#/_base/tools/support/workspace';
|
||||
import { IAgentFileSystem, IFsService } from '#/agentFs';
|
||||
import { ISessionAgentFileSystem, ISessionFsService } from '#/agentFs';
|
||||
import { IKaos } from '#/kaos';
|
||||
import { IToolRegistry } from '#/toolRegistry';
|
||||
import { IWorkspaceContext } from '#/workspaceContext';
|
||||
import { IAgentToolRegistryService } from '#/toolRegistry';
|
||||
import { ISessionWorkspaceContext } from '#/workspaceContext';
|
||||
|
||||
import { IFileToolsService } from './fileTools';
|
||||
import { IAgentFileToolsService } from './fileTools';
|
||||
import { EditTool } from './tools/edit';
|
||||
import { GlobTool } from './tools/glob';
|
||||
import { GrepTool } from './tools/grep';
|
||||
import { ReadTool } from './tools/read';
|
||||
import { WriteTool } from './tools/write';
|
||||
|
||||
export class FileToolsService implements IFileToolsService {
|
||||
export class AgentFileToolsService implements IAgentFileToolsService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@IToolRegistry toolRegistry: IToolRegistry,
|
||||
@IAgentFileSystem fs: IAgentFileSystem,
|
||||
@IAgentToolRegistryService toolRegistry: IAgentToolRegistryService,
|
||||
@ISessionAgentFileSystem fs: ISessionAgentFileSystem,
|
||||
@IKaos kaos: IKaos,
|
||||
@IWorkspaceContext workspace: IWorkspaceContext,
|
||||
@IFsService fsService: IFsService,
|
||||
@ISessionWorkspaceContext workspace: ISessionWorkspaceContext,
|
||||
@ISessionFsService fsService: ISessionFsService,
|
||||
) {
|
||||
const workspaceConfig: WorkspaceConfig = {
|
||||
workspaceDir: workspace.workDir,
|
||||
|
|
@ -46,8 +46,8 @@ export class FileToolsService implements IFileToolsService {
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IFileToolsService,
|
||||
FileToolsService,
|
||||
IAgentFileToolsService,
|
||||
AgentFileToolsService,
|
||||
InstantiationType.Delayed,
|
||||
'fileTools',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* `fileTools` domain barrel — re-exports the built-in file tools (Read / Write
|
||||
* / Edit / Grep / Glob), the shared line-ending helpers, and the
|
||||
* `IFileToolsService` registration contract + service. Importing this barrel
|
||||
* registers the `IFileToolsService` binding into the scope registry.
|
||||
* `IAgentFileToolsService` registration contract + service. Importing this barrel
|
||||
* registers the `IAgentFileToolsService` binding into the scope registry.
|
||||
*/
|
||||
|
||||
export * from './fileTools';
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
*
|
||||
* Ported from v1 (`packages/agent-core/src/tools/builtin/file/edit.ts`): the
|
||||
* `kaos.readText` / `kaos.writeText` calls become `fs.readText` /
|
||||
* `fs.writeText` against `IAgentFileSystem`, and `kaos.pathClass()` /
|
||||
* `fs.writeText` against `ISessionAgentFileSystem`, and `kaos.pathClass()` /
|
||||
* `kaos.gethome()` come from `IKaos`.
|
||||
*/
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ import { toInputJsonSchema } from '#/_base/tools/support/input-schema';
|
|||
import { literalRulePattern, matchesPathRuleSubject } from '#/_base/tools/support/rule-match';
|
||||
import type { WorkspaceConfig } from '#/_base/tools/support/workspace';
|
||||
import { renderPrompt } from '#/_base/utils/render-prompt';
|
||||
import { IAgentFileSystem } from '#/agentFs';
|
||||
import { ISessionAgentFileSystem } from '#/agentFs';
|
||||
import { IKaos } from '#/kaos';
|
||||
import { ToolAccesses } from '#/tool';
|
||||
import type { BuiltinTool, ExecutableToolResult, ToolExecution } from '#/tool';
|
||||
|
|
@ -77,7 +77,7 @@ export class EditTool implements BuiltinTool<EditInput> {
|
|||
readonly parameters: Record<string, unknown> = toInputJsonSchema(EditInputSchema);
|
||||
|
||||
constructor(
|
||||
private readonly fs: IAgentFileSystem,
|
||||
private readonly fs: ISessionAgentFileSystem,
|
||||
private readonly kaos: IKaos,
|
||||
private readonly workspace: WorkspaceConfig,
|
||||
) {}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* Ported from v1 (`packages/agent-core/src/tools/builtin/file/glob.ts`) onto
|
||||
* the v2 domains:
|
||||
* - Search root: v1 `kaos.glob(root, pattern)` (async generator) maps to
|
||||
* `fs.withCwd(root).glob(pattern)`, since v2 `IAgentFileSystem.glob`
|
||||
* `fs.withCwd(root).glob(pattern)`, since v2 `ISessionAgentFileSystem.glob`
|
||||
* searches from `fs.cwd` and returns a collected `Promise<readonly
|
||||
* string[]>`. kaos yields absolute paths, so no further joining is needed.
|
||||
* - Path safety / home expansion / path class: `resolvePathAccessPath` over
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
* instead of a misleading "No matches found".
|
||||
*
|
||||
* Documented deviation from v1: results are no longer sorted by modification
|
||||
* time. v2 `IAgentFileSystem.stat` exposes `{ isFile, isDirectory, size }`
|
||||
* time. v2 `ISessionAgentFileSystem.stat` exposes `{ isFile, isDirectory, size }`
|
||||
* only — it carries no mtime — so matches are returned in walk order (the
|
||||
* order `fs.glob` yields them, grouped by expanded sub-pattern). The cap,
|
||||
* dedup, truncation markers, and `include_dirs` filtering are unchanged.
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
import { normalize } from 'pathe';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { IAgentFileSystem } from '#/agentFs';
|
||||
import { ISessionAgentFileSystem } from '#/agentFs';
|
||||
import { IKaos } from '#/kaos';
|
||||
import { ToolAccesses } from '#/tool';
|
||||
import type { BuiltinTool, ExecutableToolResult, ToolExecution } from '#/tool';
|
||||
|
|
@ -107,7 +107,7 @@ export class GlobTool implements BuiltinTool<GlobInput> {
|
|||
readonly description: string;
|
||||
readonly parameters: Record<string, unknown> = toInputJsonSchema(GlobInputSchema);
|
||||
constructor(
|
||||
private readonly fs: IAgentFileSystem,
|
||||
private readonly fs: ISessionAgentFileSystem,
|
||||
private readonly kaos: IKaos,
|
||||
private readonly workspace: WorkspaceConfig,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* `fileTools` domain — GrepTool, the model's content search tool.
|
||||
*
|
||||
* Searches file contents with ripgrep-style regular expressions, delegating
|
||||
* the actual scan to the `agentFs` domain's `IFsService.grep` (which is
|
||||
* the actual scan to the `agentFs` domain's `ISessionFsService.grep` (which is
|
||||
* workspace-confined, `rg`-backed when available, and falls back to a Node
|
||||
* walker otherwise, honoring gitignore and glob filters). The tool maps the
|
||||
* model-facing input args onto an `FsGrepRequest`, then renders the
|
||||
|
|
@ -14,12 +14,12 @@
|
|||
* Read/Write/Edit/Grep: an explicit absolute path outside the workspace is
|
||||
* allowed for the access declaration, while a relative path that escapes the
|
||||
* workspace is rejected. The search itself is confined to the workspace by
|
||||
* `IFsService`.
|
||||
* `ISessionFsService`.
|
||||
*
|
||||
* Ported from v1 (`packages/agent-core/src/tools/builtin/file/grep.ts`). The
|
||||
* v1 tool shelled out to `rg` directly through Kaos and parsed its output;
|
||||
* that work now lives in `IFsService.grep`, so this tool only maps arguments
|
||||
* and renders results. A few v1 behaviors that `IFsService.grep` does not
|
||||
* that work now lives in `ISessionFsService.grep`, so this tool only maps arguments
|
||||
* and renders results. A few v1 behaviors that `ISessionFsService.grep` does not
|
||||
* expose (mtime ordering of `files_with_matches`, multiline matching, and
|
||||
* searching a path outside the workspace) are intentionally not replicated.
|
||||
*/
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
import type { FsGrepMatch, FsGrepRequest, FsGrepResponse } from '@moonshot-ai/protocol';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { IFsService } from '#/agentFs';
|
||||
import { ISessionFsService } from '#/agentFs';
|
||||
import { ErrorCodes, isKimiError } from '#/errors';
|
||||
import { IKaos } from '#/kaos';
|
||||
import { ToolAccesses } from '#/tool';
|
||||
|
|
@ -147,7 +147,7 @@ export class GrepTool implements BuiltinTool<GrepInput> {
|
|||
readonly description = GREP_DESCRIPTION;
|
||||
readonly parameters: Record<string, unknown> = toInputJsonSchema(GrepInputSchema);
|
||||
constructor(
|
||||
private readonly fs: IFsService,
|
||||
private readonly fs: ISessionFsService,
|
||||
private readonly kaos: IKaos,
|
||||
private readonly workspace: WorkspaceConfig,
|
||||
) {}
|
||||
|
|
@ -265,7 +265,7 @@ function renderGrepResponse(args: GrepInput, response: FsGrepResponse): Executab
|
|||
const mode: GrepMode = args.output_mode ?? 'files_with_matches';
|
||||
|
||||
// Post-filter sensitive files, mirroring v1's post-rg sensitive filter.
|
||||
// `IFsService.grep` searches the whole workspace and does not exclude
|
||||
// `ISessionFsService.grep` searches the whole workspace and does not exclude
|
||||
// sensitive paths, so the tool drops them before rendering.
|
||||
const filteredSensitive: string[] = [];
|
||||
const keptFiles = response.files.filter((file) => {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@
|
|||
*
|
||||
* Ported from v1 (`packages/agent-core/src/tools/builtin/file/read.ts`). The
|
||||
* optional `scanTextFile` / `readLineRange` / `readTailLines` fast-paths are
|
||||
* intentionally dropped: `IAgentFileSystem` streams through `readLines` only.
|
||||
* intentionally dropped: `ISessionAgentFileSystem` streams through `readLines` only.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { IAgentFileSystem } from '#/agentFs';
|
||||
import { ISessionAgentFileSystem } from '#/agentFs';
|
||||
import { IKaos } from '#/kaos';
|
||||
import { ToolAccesses } from '#/tool';
|
||||
import type { BuiltinTool, ExecutableToolResult, ToolExecution } from '#/tool';
|
||||
|
|
@ -223,7 +223,7 @@ export class ReadTool implements BuiltinTool<ReadInput> {
|
|||
readonly description = READ_DESCRIPTION;
|
||||
readonly parameters: Record<string, unknown> = toInputJsonSchema(ReadInputSchema);
|
||||
constructor(
|
||||
private readonly fs: IAgentFileSystem,
|
||||
private readonly fs: ISessionAgentFileSystem,
|
||||
private readonly kaos: IKaos,
|
||||
private readonly workspace: WorkspaceConfig,
|
||||
) {}
|
||||
|
|
@ -251,7 +251,7 @@ export class ReadTool implements BuiltinTool<ReadInput> {
|
|||
|
||||
private async execution(args: ReadInput, safePath: string): Promise<ExecutableToolResult> {
|
||||
try {
|
||||
let stat: Awaited<ReturnType<IAgentFileSystem['stat']>>;
|
||||
let stat: Awaited<ReturnType<ISessionAgentFileSystem['stat']>>;
|
||||
try {
|
||||
stat = await this.fs.stat(safePath);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* (mirroring `mkdir(parents=True, exist_ok=True)`). Path access policy is
|
||||
* resolved before any filesystem I/O.
|
||||
*
|
||||
* v2's `IAgentFileSystem.writeText` has no mode flag: overwrite maps to a
|
||||
* v2's `ISessionAgentFileSystem.writeText` has no mode flag: overwrite maps to a
|
||||
* direct write, while append reads the existing content first (treating a
|
||||
* missing file as empty) and writes the concatenation back.
|
||||
*
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
import { dirname } from 'pathe';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { AgentFileStat, IAgentFileSystem } from '#/agentFs';
|
||||
import type { AgentFileStat, ISessionAgentFileSystem } from '#/agentFs';
|
||||
import { IKaos } from '#/kaos';
|
||||
import { ToolAccesses } from '#/tool';
|
||||
import type { BuiltinTool, ExecutableToolResult, ToolExecution } from '#/tool';
|
||||
|
|
@ -62,7 +62,7 @@ export class WriteTool implements BuiltinTool<WriteInput> {
|
|||
readonly parameters: Record<string, unknown> = toInputJsonSchema(WriteInputSchema);
|
||||
|
||||
constructor(
|
||||
private readonly fs: IAgentFileSystem,
|
||||
private readonly fs: ISessionAgentFileSystem,
|
||||
private readonly kaos: IKaos,
|
||||
private readonly workspace: WorkspaceConfig,
|
||||
) {}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* keeps a JSON `FileMeta` index in the same backend under the `filestore`
|
||||
* scope. Enforces the 50 MiB upload cap while collecting the stream, prunes the
|
||||
* index when a referenced blob is missing, and hands downloads back as a lazy
|
||||
* `Readable` over `readStream`. Bound at Core scope.
|
||||
* `Readable` over `readStream`. Bound at App scope.
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
|
@ -165,7 +165,7 @@ export class FileStoreService implements IFileStore {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IFileStore,
|
||||
FileStoreService,
|
||||
InstantiationType.Delayed,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* uploaded bytes in the `IBlobStorage` backend and their `FileMeta` index in the
|
||||
* same byte store, then hands callers a stream back on download. The v1
|
||||
* `IFileStore` returned a filesystem `blobPath`; here the byte-store substrate
|
||||
* is stream-oriented, so `get` yields a `Readable` instead. Bound at Core scope.
|
||||
* is stream-oriented, so `get` yields a `Readable` instead. Bound at App scope.
|
||||
*/
|
||||
|
||||
import type { Readable } from 'node:stream';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* `filestore` domain barrel — re-exports the file-store contract, its errors,
|
||||
* and the Core-scoped implementation. Importing this barrel registers the
|
||||
* and the App-scoped implementation. Importing this barrel registers the
|
||||
* `IFileStore` binding and the file error codes into the scope/error registries.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
* flag-resolution types (`ExperimentalFeatureState`, `ExperimentalFlagConfig`,
|
||||
* `ExperimentalFlagSource`). Owns the `[experimental]` config section, whose
|
||||
* keys are flag ids and are preserved verbatim (no snake ↔ camel conversion) by
|
||||
* its TOML read/write transforms. Core-scoped — one instance shared across the
|
||||
* its TOML read/write transforms. App-scoped — one instance shared across the
|
||||
* process.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* definitions from. Definitions are contributed **decentrally**: each domain
|
||||
* calls `registerFlagDefinition` from its own `<domain>Flag.ts` top level, and
|
||||
* `FlagRegistryService` drains those contributions when it is instantiated.
|
||||
* There is no central catalog to edit by hand. Core-scoped.
|
||||
* There is no central catalog to edit by hand. App-scoped.
|
||||
*/
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* In-memory catalog of flag definitions. Seeds itself from the import-time
|
||||
* contributions (`getContributedFlags`) on construction, and also accepts
|
||||
* runtime `register` calls (used by tests). Bound at Core scope.
|
||||
* runtime `register` calls (used by tests). Bound at App scope.
|
||||
*/
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
|
|
@ -54,7 +54,7 @@ export class FlagRegistryService extends Disposable implements IFlagRegistry {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IFlagRegistry,
|
||||
FlagRegistryService,
|
||||
InstantiationType.Delayed,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
*
|
||||
* Resolves experimental flags from the environment (read through `bootstrap`),
|
||||
* the `[experimental]` config section, and defaults; reads flag definitions
|
||||
* from `flagRegistry`, and reads/watches config through `config`. Bound at Core
|
||||
* from `flagRegistry`, and reads/watches config through `config`. Bound at App
|
||||
* scope.
|
||||
*/
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ export class FlagService extends Disposable implements IFlagService {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.Core,
|
||||
LifecycleScope.App,
|
||||
IFlagService,
|
||||
FlagService,
|
||||
InstantiationType.Delayed,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export interface CompactInput {
|
|||
readonly signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface IFullCompaction {
|
||||
export interface IAgentFullCompactionService {
|
||||
readonly _serviceBrand: undefined;
|
||||
readonly isCompacting: boolean;
|
||||
|
||||
|
|
@ -21,4 +21,4 @@ export interface IFullCompaction {
|
|||
cancel(): void;
|
||||
}
|
||||
|
||||
export const IFullCompaction = createDecorator<IFullCompaction>('agentFullCompactionService');
|
||||
export const IAgentFullCompactionService = createDecorator<IAgentFullCompactionService>('agentFullCompactionService');
|
||||
|
|
|
|||
|
|
@ -15,21 +15,21 @@ import {
|
|||
import { ErrorCodes, KimiError, isKimiError, toKimiErrorPayload } from "#/errors";
|
||||
import { renderPrompt } from "#/_base/utils/render-prompt";
|
||||
import { estimateTokens, estimateTokensForMessages } from "#/_base/utils/tokens";
|
||||
import { IContextMemory } from '#/contextMemory';
|
||||
import { IContextProjector } from '#/contextProjector';
|
||||
import { IContextSizeService } from '#/contextSize';
|
||||
import { IEventSink } from '../eventSink';
|
||||
import { IExternalHooksService } from '#/externalHooks';
|
||||
import { ILLMRequester, type LLMEvent } from '#/llmRequester';
|
||||
import { IAgentContextMemoryService } from '#/contextMemory';
|
||||
import { IAgentContextProjectorService } from '#/contextProjector';
|
||||
import { IAgentContextSizeService } from '#/contextSize';
|
||||
import { IAgentEventSinkService } from '../eventSink';
|
||||
import { IAgentExternalHooksService } from '#/externalHooks';
|
||||
import { IAgentLLMRequesterService, type LLMEvent } from '#/llmRequester';
|
||||
import { isAbortError } from '#/loop/errors';
|
||||
import { retryBackoffDelays, sleepForRetry } from '#/loop/retry';
|
||||
import { IProfileService } from '#/profile';
|
||||
import { IReplayBuilderService } from '#/replayBuilder';
|
||||
import { IAgentProfileService } from '#/profile';
|
||||
import { IAgentReplayBuilderService } from '#/replayBuilder';
|
||||
import { ITelemetryService } from '#/telemetry';
|
||||
import { IToolStoreService } from '#/toolStore';
|
||||
import { ITurnService, type TurnContextOverflowContext } from '#/turn';
|
||||
import { IAgentToolStoreService } from '#/toolStore';
|
||||
import { IAgentTurnService, type TurnContextOverflowContext } from '#/turn';
|
||||
import type { ContextMessage } from '#/contextMemory';
|
||||
import { IWireRecord } from '#/wireRecord';
|
||||
import { IAgentWireRecordService } from '#/wireRecord';
|
||||
import {
|
||||
TODO_STORE_KEY,
|
||||
renderTodoList,
|
||||
|
|
@ -37,7 +37,7 @@ import {
|
|||
} from '#/todoList/tools/todo-list';
|
||||
import compactionInstructionTemplate from './compaction-instruction.md?raw';
|
||||
import {
|
||||
IFullCompaction,
|
||||
IAgentFullCompactionService,
|
||||
type CompactInput,
|
||||
type FullCompactionCompleteData,
|
||||
} from './fullCompaction';
|
||||
|
|
@ -88,7 +88,7 @@ class CompactionTruncatedError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export class FullCompactionService extends Disposable implements IFullCompaction {
|
||||
export class AgentFullCompactionService extends Disposable implements IAgentFullCompactionService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
private readonly strategy: CompactionStrategy;
|
||||
private compactionCountInTurn = 0;
|
||||
|
|
@ -96,18 +96,18 @@ export class FullCompactionService extends Disposable implements IFullCompaction
|
|||
|
||||
constructor(
|
||||
private readonly options: FullCompactionServiceOptions = {},
|
||||
@IContextMemory private readonly context: IContextMemory,
|
||||
@IContextProjector private readonly projector: IContextProjector,
|
||||
@IContextSizeService private readonly contextSize: IContextSizeService,
|
||||
@ILLMRequester private readonly llmRequester: ILLMRequester,
|
||||
@IProfileService private readonly profile: IProfileService,
|
||||
@IToolStoreService private readonly toolStore: IToolStoreService,
|
||||
@IAgentContextMemoryService private readonly context: IAgentContextMemoryService,
|
||||
@IAgentContextProjectorService private readonly projector: IAgentContextProjectorService,
|
||||
@IAgentContextSizeService private readonly contextSize: IAgentContextSizeService,
|
||||
@IAgentLLMRequesterService private readonly llmRequester: IAgentLLMRequesterService,
|
||||
@IAgentProfileService private readonly profile: IAgentProfileService,
|
||||
@IAgentToolStoreService private readonly toolStore: IAgentToolStoreService,
|
||||
@ITelemetryService private readonly telemetry: ITelemetryService,
|
||||
@IWireRecord private readonly wireRecord: IWireRecord,
|
||||
@IEventSink private readonly events: IEventSink,
|
||||
@IReplayBuilderService private readonly replayBuilder: IReplayBuilderService,
|
||||
@IExternalHooksService private readonly externalHooks: IExternalHooksService,
|
||||
@ITurnService turnService: ITurnService,
|
||||
@IAgentWireRecordService private readonly wireRecord: IAgentWireRecordService,
|
||||
@IAgentEventSinkService private readonly events: IAgentEventSinkService,
|
||||
@IAgentReplayBuilderService private readonly replayBuilder: IAgentReplayBuilderService,
|
||||
@IAgentExternalHooksService private readonly externalHooks: IAgentExternalHooksService,
|
||||
@IAgentTurnService turnService: IAgentTurnService,
|
||||
) {
|
||||
super();
|
||||
this.strategy =
|
||||
|
|
@ -579,17 +579,17 @@ function isTodoItem(value: unknown): value is TodoItem {
|
|||
);
|
||||
}
|
||||
|
||||
export { FullCompactionService as FullCompaction };
|
||||
export { AgentFullCompactionService as FullCompaction };
|
||||
|
||||
// Construct eagerly (not delayed): the service registers turn-lifecycle hooks
|
||||
// (onLaunched / beforeStep / afterStep) in its constructor that drive auto
|
||||
// compaction. With delayed instantiation the eager `accessor.get(IFullCompaction)`
|
||||
// compaction. With delayed instantiation the eager `accessor.get(IAgentFullCompactionService)`
|
||||
// only realizes a proxy, so the hooks would not register until the first RPC —
|
||||
// after turns have already run without the auto-compaction gate.
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IFullCompaction,
|
||||
FullCompactionService,
|
||||
IAgentFullCompactionService,
|
||||
AgentFullCompactionService,
|
||||
InstantiationType.Eager,
|
||||
'fullCompaction',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Defines the public contracts of the gateway layer: the `IRestGateway` /
|
||||
* `IWSGateway` / `IWSBroadcastService` entry points. Session scope creation is
|
||||
* owned by `session-lifecycle`; the gateway resolves sessions through it.
|
||||
* Core-scoped — shared across the application.
|
||||
* App-scoped — shared across the application.
|
||||
*/
|
||||
|
||||
import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiation';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
*
|
||||
* Owns the REST/WS entry points; resolves sessions through `session-lifecycle`,
|
||||
* agents through `agent-lifecycle`, drives turns through `turn`, flushes logs
|
||||
* through `log`, and subscribes to broadcasts through `event`. Bound at Core
|
||||
* through `log`, and subscribes to broadcasts through `event`. Bound at App
|
||||
* scope.
|
||||
*/
|
||||
|
||||
|
|
@ -12,11 +12,11 @@ import { InstantiationType } from '#/_base/di/extensions';
|
|||
import { type IScopeHandle, LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { Disposable } from '#/_base/di/lifecycle';
|
||||
import { IAgentLifecycleService } from '#/agent-lifecycle/agentLifecycle';
|
||||
import { IEventSink } from '#/eventSink';
|
||||
import { IAgentEventSinkService } from '#/eventSink';
|
||||
import { ILogService, ISessionLogService } from '#/log';
|
||||
import { IPromptService } from '#/prompt';
|
||||
import { IAgentPromptService } from '#/prompt';
|
||||
import { ISessionLifecycleService } from '#/session-lifecycle';
|
||||
import { ITurnService } from '#/turn';
|
||||
import { IAgentTurnService } from '#/turn';
|
||||
|
||||
import { IRestGateway, IWSBroadcastService, IWSGateway } from './gateway';
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ export class RestGateway implements IRestGateway {
|
|||
agentId: string,
|
||||
input: string,
|
||||
): Promise<{ readonly turn_id: number } | undefined> {
|
||||
const turn = this.agent(sessionId, agentId).accessor.get(IPromptService).prompt({
|
||||
const turn = this.agent(sessionId, agentId).accessor.get(IAgentPromptService).prompt({
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: input }],
|
||||
toolCalls: [],
|
||||
|
|
@ -56,17 +56,17 @@ export class RestGateway implements IRestGateway {
|
|||
content: string,
|
||||
): Promise<{ readonly turn_id: number } | undefined> {
|
||||
const agent = this.agent(sessionId, agentId);
|
||||
const turn = agent.accessor.get(IPromptService).steer({
|
||||
const turn = agent.accessor.get(IAgentPromptService).steer({
|
||||
role: 'user',
|
||||
content: [{ type: 'text', text: content }],
|
||||
toolCalls: [],
|
||||
origin: { kind: 'user' },
|
||||
});
|
||||
const id = turn?.id ?? agent.accessor.get(ITurnService).getActiveTurn()?.id;
|
||||
const id = turn?.id ?? agent.accessor.get(IAgentTurnService).getActiveTurn()?.id;
|
||||
return Promise.resolve(id === undefined ? undefined : { turn_id: id });
|
||||
}
|
||||
cancel(sessionId: string, agentId: string, reason?: string): Promise<void> {
|
||||
const activeTurn = this.agent(sessionId, agentId).accessor.get(ITurnService).getActiveTurn();
|
||||
const activeTurn = this.agent(sessionId, agentId).accessor.get(IAgentTurnService).getActiveTurn();
|
||||
activeTurn?.abortController.abort(reason);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ export class WSGateway implements IWSGateway {
|
|||
|
||||
constructor(
|
||||
@ISessionLifecycleService _sessions: ISessionLifecycleService,
|
||||
@IEventSink _event: IEventSink,
|
||||
@IAgentEventSinkService _event: IAgentEventSinkService,
|
||||
) {}
|
||||
|
||||
connect(connectionId: string): void {
|
||||
|
|
@ -104,7 +104,7 @@ export class WSGateway implements IWSGateway {
|
|||
export class WSBroadcastService extends Disposable implements IWSBroadcastService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(@IEventSink event: IEventSink) {
|
||||
constructor(@IAgentEventSinkService event: IAgentEventSinkService) {
|
||||
super();
|
||||
this._register(
|
||||
event.on(() => {
|
||||
|
|
@ -113,6 +113,6 @@ export class WSBroadcastService extends Disposable implements IWSBroadcastServic
|
|||
}
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.Core, IRestGateway, RestGateway, InstantiationType.Delayed, 'gateway');
|
||||
registerScopedService(LifecycleScope.Core, IWSGateway, WSGateway, InstantiationType.Delayed, 'gateway');
|
||||
registerScopedService(LifecycleScope.Core, IWSBroadcastService, WSBroadcastService, InstantiationType.Delayed, 'gateway');
|
||||
registerScopedService(LifecycleScope.App, IRestGateway, RestGateway, InstantiationType.Delayed, 'gateway');
|
||||
registerScopedService(LifecycleScope.App, IWSGateway, WSGateway, InstantiationType.Delayed, 'gateway');
|
||||
registerScopedService(LifecycleScope.App, IWSBroadcastService, WSBroadcastService, InstantiationType.Delayed, 'gateway');
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export interface GoalReasonInput {
|
|||
readonly reason?: string;
|
||||
}
|
||||
|
||||
export interface IGoalService {
|
||||
export interface IAgentGoalService {
|
||||
readonly _serviceBrand: undefined;
|
||||
getGoal(): GoalToolResult;
|
||||
createGoal(input: CreateGoalInput, actor?: GoalActor): Promise<GoalSnapshot>;
|
||||
|
|
@ -26,4 +26,4 @@ export interface IGoalService {
|
|||
markBlocked(input?: GoalReasonInput, actor?: GoalActor): Promise<GoalSnapshot | null>;
|
||||
}
|
||||
|
||||
export const IGoalService = createDecorator<IGoalService>('agentGoalService');
|
||||
export const IAgentGoalService = createDecorator<IAgentGoalService>('agentGoalService');
|
||||
|
|
|
|||
|
|
@ -7,18 +7,18 @@ import {
|
|||
Disposable,
|
||||
} from "#/_base/di";
|
||||
import { ErrorCodes, KimiError } from "#/errors";
|
||||
import { IContextInjector } from '../contextInjector';
|
||||
import { IEventSink } from '../eventSink';
|
||||
import { IPermissionModeService } from '#/permissionMode';
|
||||
import { IReplayBuilderService } from '#/replayBuilder';
|
||||
import { ISystemReminderService } from '#/systemReminder';
|
||||
import { IAgentContextInjectorService } from '../contextInjector';
|
||||
import { IAgentEventSinkService } from '../eventSink';
|
||||
import { IAgentPermissionModeService } from '#/permissionMode';
|
||||
import { IAgentReplayBuilderService } from '#/replayBuilder';
|
||||
import { IAgentSystemReminderService } from '#/systemReminder';
|
||||
import type { TelemetryProperties } from '#/telemetry';
|
||||
import { ITelemetryService } from '#/telemetry';
|
||||
import { IToolRegistry } from '#/toolRegistry';
|
||||
import { IAgentToolRegistryService } from '#/toolRegistry';
|
||||
import type { WireRecord } from '#/wireRecord';
|
||||
import { IWireRecord } from '#/wireRecord';
|
||||
import { IAgentWireRecordService } from '#/wireRecord';
|
||||
import {
|
||||
IGoalService,
|
||||
IAgentGoalService,
|
||||
type GoalReasonInput,
|
||||
} from './goal';
|
||||
import {
|
||||
|
|
@ -94,21 +94,21 @@ interface GoalState {
|
|||
terminalReason?: string;
|
||||
}
|
||||
|
||||
export class GoalService extends Disposable implements IGoalService {
|
||||
export class AgentGoalService extends Disposable implements IAgentGoalService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private state: GoalState | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly options: GoalServiceOptions = {},
|
||||
@IWireRecord private readonly wireRecord: IWireRecord,
|
||||
@IEventSink private readonly events: IEventSink,
|
||||
@ISystemReminderService private readonly reminders: ISystemReminderService,
|
||||
@IReplayBuilderService private readonly replayBuilder: IReplayBuilderService,
|
||||
@IAgentWireRecordService private readonly wireRecord: IAgentWireRecordService,
|
||||
@IAgentEventSinkService private readonly events: IAgentEventSinkService,
|
||||
@IAgentSystemReminderService private readonly reminders: IAgentSystemReminderService,
|
||||
@IAgentReplayBuilderService private readonly replayBuilder: IAgentReplayBuilderService,
|
||||
@ITelemetryService private readonly telemetry: ITelemetryService,
|
||||
@IContextInjector private readonly dynamicInjector: IContextInjector,
|
||||
@IToolRegistry toolRegistry: IToolRegistry,
|
||||
@IPermissionModeService private readonly permissionMode: IPermissionModeService,
|
||||
@IAgentContextInjectorService private readonly dynamicInjector: IAgentContextInjectorService,
|
||||
@IAgentToolRegistryService toolRegistry: IAgentToolRegistryService,
|
||||
@IAgentPermissionModeService private readonly permissionMode: IAgentPermissionModeService,
|
||||
) {
|
||||
super();
|
||||
this._register(
|
||||
|
|
@ -595,8 +595,8 @@ function normalizeCompletionCriterion(value: string | undefined): string | undef
|
|||
|
||||
registerScopedService(
|
||||
LifecycleScope.Agent,
|
||||
IGoalService,
|
||||
GoalService,
|
||||
IAgentGoalService,
|
||||
AgentGoalService,
|
||||
InstantiationType.Delayed,
|
||||
'goal',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* `goal` domain barrel — re-exports the goal contract (`goal`) and its scoped
|
||||
* service (`goalService`). Importing this barrel registers the `IGoalService`
|
||||
* service (`goalService`). Importing this barrel registers the `IAgentGoalService`
|
||||
* binding into the scope registry.
|
||||
*/
|
||||
|
||||
|
|
|
|||
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