Move config-section registration out of Service constructors and into
import-time contributions, mirroring the existing registerScopedService
pattern so a section is available as soon as its domain barrel is imported.
- add configSectionContributions collector (module-level, no DI) with
registerConfigSection / get / _clearForTests
- ConfigRegistry drains the collected contributions on construction
- each owner configSection.ts calls registerConfigSection at top level
(providers, models, modelCatalog, experimental, thinking/defaultThinking,
loopControl, permission, hooks, background, microCompaction, cron)
- domain barrels import their configSection to guarantee the side effect
- remove registerSection calls and the now-unused IConfigRegistry injection
from the 11 owning Services
The KIMI_MODEL_* effective overlay is still registered by modelService and
left for a follow-up.
Replace the god-object `IKaos` interface and the `@moonshot-ai/kaos`
package dependency with atomic services:
- `IHostEnvironment` (App scope) — memoised OS / shell / path-style /
home probe; sync fields plus a `ready` promise the composition root
awaits before seeding Session scopes.
- `IExecContext` (Session scope) — immutable cwd + env-layers seed
value with `withCwd` / `withEnv` derivations; replaces
`IKaos.cwd/withCwd/withEnv` and `IKaosFactory`.
- `ISessionAgentFileSystem` / `ISessionProcessRunner` — inline
`node:fs/promises` and `child_process.spawn` directly; the
`IKaos.backend` leak that let `profile/context.ts`, `fileTools/glob`
and `agentFs/runRg` reach around the facades is gone.
Vendor the pure helpers into `src/_base/execEnv/` (`BufferedReadable`,
`decodeTextWithErrors`, `globPatternToRegex`, host-environment probe).
`src/app/kaos/` and `test/kaos/` are deleted; session-lifecycle awaits
`hostEnv.ready` and then seeds `IExecContext` into the child scope.
Rewrites every `@IKaos` consumer:
- `shellTools/bash`, `fileTools/{read,write,edit,glob,grep}`,
`media/read-media`, `_base/tools/policies/path-access`
- `permissionPolicy/git-cwd-write-approve` and
`permissionPolicy/git-control-path-access-ask`
- `agent/rpc`, `agent/agentTool`, `agent/profile/{context,profileService}`
- `session/{sessionWarning,workspaceContext}`
- `app/bootstrap` (drops `IBootstrapService.detect()`)
- `fileTools/glob` and `agentFs/runRg` now route rg through
`ISessionProcessRunner.exec` with per-call `{ cwd }` overrides
`test/tools/fixtures/fake-kaos.ts` is replaced by `fake-exec.ts` with
per-atom factories (`createFakeHostEnvironment`, `createFakeExecContext`,
`createFakeAgentFs`, `createFakeProcessRunner`). Domain-layer registry,
PUML diagram, tsdown externals and `package.json` deps are updated
accordingly.
`packages/kaos/` stays in the workspace — v1, node-sdk and acp-adapter
still depend on it. Only `agent-core-v2` has been detached.
- Inline SessionCronStore, CronScheduler, and CronPersistence into
AgentCronService: state lives as private fields, the poll loop uses a
shared timer, and persistence calls IAtomicDocumentStore directly.
- Drop CronToolManager; the cron tools take IAgentCronService directly,
matching the background domain.
- Add a generic IntervalTimer to _base/utils and use it for the poll loop.
- Move cron-expr/jitter/clock to the cron root, merge time-format and
cron-fire-xml into format.ts, and fold the cron types and telemetry
constants into the contract and service.
- remove SubAgentHost / ISessionSubagentHost; a subagent is now a plain
agent scope created via IAgentLifecycleService and driven through the
child agent's own turn/loop services
- add IAgentLifecycleService.fork(parentAgentId); startBtw forks main
- move the Agent collaboration tool into the new agentTool domain
(stateless runChildAgent helpers, no runner class, no subagent service)
- move SubagentBatch + runChildAgentQueued into the swarm domain
- seed IAgentScopeContext (agentId) per agent so the Agent tool and swarm
can name themselves as the parent
The v2 `WSBroadcastService` was an empty scaffolding class — its
constructor only registered a no-op `event.on(() => {})` and no other
Service or external caller ever resolved `IWSBroadcastService`. In
server-v2 the WS event fan-out (seq/epoch, journaling, replay,
per-connection dispatch) is done by `SessionEventBroadcaster`, a plain
class in the transport layer that consumes `IEventService`,
`IAgentEventSinkService`, `IAgentLifecycleService`, and
`ISessionLifecycleService` directly.
- Remove `WSBroadcastService` impl and its `registerScopedService`
binding from `app/gateway/gatewayService.ts`.
- Remove `IWSBroadcastService` interface and decorator from
`app/gateway/gateway.ts`.
- Update the gateway barrel / header comments to stop advertising it,
and add a note that WS event fan-out is a transport concern owned by
the edge package.
- Replace the `docs/di.md` "release resources" example, which used the
removed stub, with `FlagService` — a real Disposable that registers a
`config.onDidChangeConfiguration` subscription via `_register`.
- Drop the `IWSBroadcastService` entry and the stub-only
`gateway ..> event : subscribe` edge from
`docs/di-scope-domains.puml`. The SVG needs to be re-rendered with
plantuml; not regenerated here.
- plugin: fingerprint the analyzer output (services/edges/unknownTokens,
excluding generatedAt) and only invalidate the virtual module on real
content diffs, so ordinary edits no longer trigger a browser reload.
- web viewer: persist the ReactFlow viewport (x/y/zoom) to sessionStorage
and restore on mount, so reloads no longer snap back to fitView.
- analyzer: expose each service's public interface surface via a new
optional ServiceNode.publicMembers (methods + property signatures,
_serviceBrand filtered out), and register PRODUCTION_OVERRIDES so
bootstrap-seeded bindings resolve to their real backends.
- web viewer: seed node inPorts from publicMembers so uncalled methods
still render; dim the handle and label of ports with no incoming edge
so the connected-vs-declared distinction reads at a glance.
- web viewer: extend search to also match publicMembers, and turn search
into a highlight/dim treatment (matches + neighbors stay bright, the
rest dim) instead of filtering — matched nodes get a cyan outline
distinct from the yellow selection outline.
- Host resolveThinkingEffort/resolveThinkingLevel in the profile domain,
the owner of the thinking/defaultThinking config sections, so they use
the authoritative ThinkingConfig from configSection.ts.
- Drop the local ThinkingConfigDefaults structural duplicate that existed
only to keep config (L2) from importing upward into profile (L4).
- Update profile/profileService consumers and the config barrel, and move
the test alongside the helpers.
- analyzer: record fromMethod/toMethod on each EdgeRef, generalize
event-bus field detection to all DI-injected ctor fields, and
attribute this.<field>.<method>() and .get(IX).<method>() call sites
- analyzer: seed IAgentScopeContext framework binding so Agent-scope
edges resolve instead of showing up as unresolved
- web: render per-method in/out ports on nodes, route each edge to the
matching method handle, size nodes by port count, and show an
expandable call list in the edge panel
Rename bare `onDidChange` to semantic `onDidChange<Facet>` following the
VSCode event-naming convention (the event name carries what changed), and
carry a payload so consumers know what changed without re-reading:
- IConfigService.onDidChangeConfiguration
- IProviderService.onDidChangeProviders ({ added, removed, changed })
- IModelService.onDidChangeModels ({ added, removed, changed })
- ISessionMetadata.onDidChangeMetadata ({ changed: (keyof SessionMeta)[] })
- ISessionInteractionService.onDidChangePending ({ pending: string[] })
Add ISessionLifecycleService session-lifecycle events (parity with v1):
onDidCreateSession / onDidCloseSession / onDidArchiveSession /
onDidForkSession, fired from create / close / archive / fork.
Add IEventService.onDidPublish: Event<DomainEvent> so the bus matches the
typed Event<T> convention used elsewhere (publish/subscribe retained).
Update tests, examples, and docs to the new names; add payload and
lifecycle-event assertions.
- remove registerSingleton / getSingletonServiceDescriptors / _clearRegistryForTests and their backing registry from _base/di/extensions.ts
- keep InstantiationType, still imported via #/_base/di/extensions across v2
- drop the stale "legacy exports" note for these helpers from v2 docs and the agent-core-dev skill
Adds a dev-only tool under `scripts/dep-graph/` that statically analyses the
DI service graph and serves it as an interactive React Flow viewer:
- `analyzer/`: ts-morph pass over `src/**/*.ts` extracts every
`registerScopedService` binding as a node keyed by `${scope}::${token}`,
then records ctor / accessor / publish / subscribe / emit / on edges. Each
edge is resolved to the concrete impl visible from the source's scope
(walking source scope up to App); if no binding is visible the edge is
marked `unresolved` — the exact signal for a container-construction
failure. Framework tokens (`IKaos`, `ISessionContext`, …) are seeded so
they don't sink into false-positive unresolveds.
- `plugin/virtual-dep-graph.ts`: Vite plugin that exposes the analyzer
output as a `virtual:dep-graph` module, mirrors it to
`.local/dep-graph.json`, and re-analyses on any `src/**/*.ts` change via
chokidar with a 200 ms debounce, then invalidates the virtual module for
HMR.
- `web/`: React + React Flow frontend with dagre auto-layout in RL mode
(base primitives on the left, facades on the right). Sidebar filters by
scope / edge kind / domain / search; toggles for `hide orphans` and
`group by scope` (horizontal App | Session | Agent bands). Isolated
nodes are pinned to the sink rank so they sit with the base primitives.
- `cli.ts` (`pnpm dep-graph:analyze`): one-shot JSON dump for CI / offline
inspection.
- `lint.ts` (`pnpm dep-graph:lint`): treats unresolved ctor edges as
errors (container will crash) and unresolved accessor edges as warnings
(only safe under an active inner scope). Auto-runs the analyzer when the
snapshot is stale.
Isolation from deploy: everything lives under `scripts/dep-graph/` and is
never referenced from `src/index.ts`, so `tsdown` doesn't bundle it into
`dist/`. The added `ts-morph`, `vite`, `react`, `react-dom`,
`@vitejs/plugin-react`, `@xyflow/react`, `@dagrejs/dagre`, `tsx`, and
`@types/react*` all land in `devDependencies` — `pnpm install --prod`
skips them.
Also adds `.vite/` to `.gitignore` so Vite's per-package dep pre-bundling
cache isn't tracked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move src/{domain}/* to src/{scope}/{domain}/* (app/session/agent) so the
directory layout mirrors the DI LifecycleScope tree. Split the three
multi-scope domains so no domain spans two scopes:
- log -> app/log + session/sessionLog
- question -> session/question + agent/questionTools
- skill -> app/globalSkillCatalog + session/sessionSkillCatalog + agent/skill
Rewrite every #/ and relative import, module augmentation, vi.mock, and
source-path read; update the package barrel, registerScopedService domain
labels, the domain-layer checker, the dep-graph script, file headers, and
the di-scope-domains map.
- add config-driven model catalog refresh scheduler (interval / on-start)
- refresh all providers and add the single-provider refresh endpoint
- broadcast model_catalog.changed to session subscribers
- finish wiring route handlers, middleware, and auth services
- align route and websocket tests with the authenticated server model
- add DefaultSessionSubagentHost with spawn/resume/retry, runQueued swarm
with a concurrency cap, startBtw side-question agents, cancelAll and the
subagent.suspended event, onReady on first turn activity, and telemetry
- add the model-facing Agent collaboration tool (foreground/background,
resume, gated on task tools) with explore git-context injection and a
summary-continuation handoff
- fire SubagentStart/SubagentStop external hooks and add a registerable
deny-all permission policy used by side-question agents
- add profile/context.ts to load the user- and project-level AGENTS.md
hierarchy with a 32KB soft budget
- add AgentProfileService.applyProfile as the production entry point that
assembles SystemPromptContext and renders the profile
- add SessionWarningService as the getSessionWarnings producer, surfacing
agents-md-oversized instead of silently truncating
- cover loading, applyProfile, and the warning surface with tests
- add agentFs/rgLocator: shared ripgrep resolution (system PATH, then optional
cached fallback) driven through a caller-supplied RgProbe
- add agentFs/runRg: shared Glob rg subprocess plumbing on IKaos
(timeout/abort, capped output, two-phase kill, EAGAIN single-thread retry)
- rewrite GlobTool to use the shared locator/runner and track fallbacks
- make Grep resolve rg through the shared locator and emit
fs_grep_node_fallback telemetry when rg is missing
- add agentFs/gitContext.collectGitContext and prepend a sanitized
<git-context> block to fresh explore subagent prompts
- add noopTelemetryService for tools constructed outside DI
- cover gitContext, glob, fsService, and fileToolsService with tests
- add requestBuild/serverFirstToken and serverDecode/clientConsume fields to LLMStreamTiming and LoopStepEndEvent
- forward the split timing through AgentLoopService event and streamTiming mapping
- inject ILogService into AgentLoopService and emit a per-step 'llm response' log via logStepTiming to attribute slow turns
- replace `Core` with `App` across the skill docs and dep-graph.mjs
- drop the `Turn` scope, collapsing the four-tier tree to three (App/Session/Agent)
- update examples, anti-patterns, banned entity-service names, and createCoreScope -> createAppScope
- 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
- stamp `msg_<ulid>` on every message entering IContextMemory and persist it
in the context.splice wire record, so ids survive restore
- carry the provider response id as `providerMessageId` on assistant messages
- thread `promptMessageId` from prompt -> turn -> turn.started ->
InFlightTurnTracker, so snapshot `current_prompt_id` comes from the native
path instead of post-hoc enrichment
- project real ids on the wire and look up messages by id; drop the positional
parseMessageId
- make the v1 prompt_id equal its user message id
* feat(server): add GUI store API mirroring localStorage
- add /api/v1/gui/store/* endpoints (getItem/setItem/removeItem/clear/length) mirroring the browser localStorage interface
- add IGuiStoreService persisting opaque string values to ~/.kimi-code/gui.toml via smol-toml with atomic writes and an in-process write lock
- wire protocol schema, service, routes, and DI registration; add e2e tests and update the API surface snapshot
* chore: add changeset for gui store api
* fix(server): harden GUI store key handling and file permissions
- use a null-prototype record and an own-property check so keys that exist on Object.prototype (toString, constructor, __proto__) behave like ordinary keys
- write gui.toml with 0600 permissions so unsent drafts and input history stay private to the owning user
- add MessageLegacyService (agent-core-v2 L7 edge adapter) implementing the
v1 GET /sessions/{sid}/messages contract on top of the native services
- cold sessions: load + restore the main agent wire log and read the full
transcript from IReplayBuilderService; live sessions keep reading
IContextMemory
- rewrite the messages route as a thin adapter over the legacy service
- register the message.not_found (40403) error code in protocol and
agent-core-v2
The telemetry toggle was hidden unless the config explicitly set a value, and its on/off mapping treated the default (unset) state as off even though telemetry is enabled by default. Show it always, treat unset/true as on, and rename it with a description and a restart note.
- forward undo and archive results from ISessionLegacyService instead of
re-paginating undo in the route and hardcoding the archive response
- drop the AUTH_TOKEN_MISSING error mapping that v1 does not declare
- move the ContextMessage -> Message projection into agent-core-v2 so
the session legacy service owns the undo response shape
The v2 readiness probe returned a simplified snapshot: default_model was
hardcoded to null, providers_count counted only oauth providers, and
managed_provider was synthesized from any authenticated provider. Mirror
v1's AuthSummaryService.get() through a new L7 edge adapter.
- add IAuthLegacyService projecting provider/config/oauth state into the
v1 AuthSummary wire shape; the native IAuthSummaryService keeps serving
/api/v2 untouched
- default_model reads the configured defaultModel
- providers_count counts every configured provider
- managed_provider reflects managed:kimi-code cached-token state and is
null when that provider is absent
- ready matches v1 (providers >= 1, default model set, not revoked)
- register authLegacy at L7 in the domain-layer map
- read .git/HEAD and peel ref: refs/heads/<branch> to populate branch
- resolve the real git dir through a .git worktree/submodule file
- keep detached HEAD and unreadable files as branch: null
- add branch-resolution tests for slash branches, detached HEAD, and worktrees
* feat(timing): split TTFT into api-server and client portions
Time-to-first-token previously lumped in-process request building
(message serialization, param assembly) together with network + server
latency, making it impossible to tell whether a slow turn was the client
or the API server.
Add an `onRequestSent` hook to kosong's GenerateOptions, fired by every
provider immediately before it dispatches the network call. The window
from request start to dispatch is attributed to the client; the window
from dispatch to the first streamed token is attributed to the API
server. The split flows through the step.end / turn.step.completed
events (and therefore wire.jsonl) and is surfaced in three places:
- KIMI_CODE_DEBUG=1: `TTFT: 2.5s (api 2.4s + client 100ms)`
- session log: new `llm response` line with the timing breakdown
- vis: firstToken/api + firstToken/client rows and timeline label
The split is omitted (total only) when a provider does not report the
boundary, preserving backward compatibility.
* feat(timing): split the decode window into server vs client time
Time-to-first-token now reports a client/server split, but the slow part
of a long turn is the decode window (inter-token streaming), which was
still a single opaque number. Profiling long sessions showed decode
throughput halving over a session's lifetime independent of context
size, which the synchronous per-chunk stream pipeline can cause: kosong
awaits the host callback for every streamed part, so a loaded main
thread throttles how fast tokens are pulled off the wire.
Account for this directly in the stream loop: the time awaiting the next
part (server + network) versus the time spent processing each part
in-process (deep copy, host callback, part merge). The split is reported
through onStreamEnd and flows through the step.end / turn.step.completed
events (and wire.jsonl) into the same three surfaces as the TTFT split:
- KIMI_CODE_DEBUG=1: `TPS: 40.0 tok/s (200 tokens in 5.0s; server 4.6s + client 400ms)`
- session log: serverDecodeMs / clientConsumeMs on the `llm response` line
- vis: streamDuration/server + streamDuration/client rows and timeline label
A large, growing client share confirms host-side throttling; a dominant
server share points at the server/connection. The per-chunk accounting is
wrapped in try/finally so it stays correct across `continue` and aborts,
and is omitted when the stream reports nothing.
- background: add TaskList / TaskOutput / TaskStop and register them in BackgroundService
- goal: add CreateGoal / GetGoal / SetGoalBudget / UpdateGoal; extend IGoalService with markComplete, markBlocked, setBudgetLimits
- skill: register the Skill tool in AgentSkillService
- question: add AskUserQuestion (foreground + background) via a new Agent-scoped QuestionToolsService
- web: add FetchURL / WebSearch with LocalFetchURL and Moonshot fetch/search providers; add @mozilla/readability and linkedom deps
- move ToolResultBuilder into the tool domain so it can be shared across tool domains
- wire IQuestionToolsService and IWebService into AgentRPCService so the new registration services are instantiated
- surface custom metadata in session index summaries so child sessions can be filtered without per-session document reads
- add ISessionLegacyService.createChild/listChildren: children are forks tagged with parent_session_id + child_session_kind, listed by those markers
- wire GET/POST /sessions/{id}/children and GET /sessions/{id}/warnings, reusing the protocol schemas and mapping session.not_found / session.fork_active_turn
- register the sessionLegacy domain at L7 in the domain-layer check
- replace node:fs fs.watch with a chokidar FSWatcher on the parent
directory (depth 0), filtering events by normalized path so the
match is correct on Windows as well as POSIX
- keep the existing Event<void> contract, 150ms debounce, and
ref-counted arm/disarm lifecycle
- add chokidar ^4.0.3 and refresh the lockfile
- settle the watcher in storage watch tests to account for
chokidar's asynchronous OS-watcher attachment
Bring the v1 WebSocket protocol to server-v2 so web clients get gap-free
sync across reconnects instead of silently losing events.
- per-session durable event journal with monotonic seq + epoch, recovered
across restarts
- SessionEventBroadcaster: single per-session fan-out, durable/volatile
classification, and cursor-based replay (buffer_overflow / epoch_changed)
- WsConnectionV1: client_hello / subscribe with cursors, replay or
resync_required, and ack carrying authoritative server cursors
- GET /sessions/:id/snapshot: atomic-at-a-watermark state and in-flight turn
- IAgentLifecycleService: onDidCreate / onDidDispose for agent discovery
- GET /connections: list live WebSocket clients
- add IWorkspaceStore + FileWorkspaceStore persisting the catalog to <homeDir>/workspaces.json in the v1-compatible schema
- WorkspaceRegistryService loads from the store, caches in memory, and writes through on create/update/delete
- when workspaces.json is absent or malformed, rebuild from the legacy session_index.jsonl (one workspace per distinct workDir)
- add tests for cross-instance persistence, rebuild, and write-through
- register @fastify/swagger before routes and serve GET /openapi.json
- add v2-specific openapi transform for multipart upload, binary downloads, and the fs-action/question oneOf dispatchers
- project the session-action dispatcher into archive only (v2 registers a subset of v1 routes)
- reuse protocol wire schemas, no inline re-declaration
- resolve every config-section owner against one shared IConfigRegistry
- add register+inspect scenario asserting all expected sections
- add write+round-trip scenario persisting every persistable section
- stub non-config collaborators to construct owners in isolation
Typing `/\' in shell mode (`!\') now triggers file path completion instead of the slash command menu, for both a bare leading `/\' and inline paths like `ls /\'. Hidden entries are skipped to match `/add-dir\', and accepting a completion no longer produces a double leading slash.
Collapse registered workspaces that share a root in the daemon registry (preferring the canonical id) and in the web sidebar merge, so the same folder no longer renders as two identical, synchronously-selected entries.