Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
20 changes: 19 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,29 @@ jobs:
- run: npm test
- run: npm run pack

windows-kimi:
runs-on: windows-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
node: [22, 26]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm run test:kimi
- run: npm run pack

# A release is a tag vX.Y.Z a maintainer pushes on main; nothing on main names a version. The tag's version is
# stamped into every packed manifest, and the release branch, which the hosts install from (Claude Code, WorkBuddy,
# Grok and Qoder through their marketplaces, Codex at --ref, Antigravity, OpenCode and DeepSeek Harness from a
# clone), moves one commit on, with the tag re-pointed there. A push made with this workflow's token starts no run.
publish:
needs: test
needs: [test, windows-kimi]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
timeout-minutes: 10
Expand All @@ -50,6 +67,7 @@ jobs:
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
- run: npm ci
- run: node scripts/pack.mjs all dist "--version=$GITHUB_REF_NAME"
- run: scripts/publish-tag.sh "$GITHUB_REF_NAME" release
env:
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ The plugin is the access layer: shared logic lives in `lib/`, and each host is a
| DeepSeek Harness (dsh) | Supported | `hosts/dsh`, an in-process cordis plugin; installs `dist/dsh` |
| Grok CLI 1.0.70+ | Supported | `hosts/grok`, command hooks; the strategy reaches the model at the turn's first tool result; installs `dist/grok` |
| Qoder CLI (`qodercli`) 1.1.79+ | Supported | `hosts/qoder`, command hooks; installs `dist/qoder` |
| Kimi Code CLI 2.4.1 and Desktop 1.2.1 | Direct install; Windows native checked | `hosts/kimi`, native hooks and isolated ACP distillation; installs `dist/kimi` |
| Muse Code | Not yet | No reviewed adapter yet |
| OpenAI Dots, Claude Duet, Grok Bot, Meta Muse | Not yet | Cloud agents without hooks; need Evolver's remote MCP and routines |

Expand Down Expand Up @@ -135,6 +136,16 @@ injections, undelivered reports), so nothing is lost on the way over.
prompt box (`evolver-status`, with or without its `/`) and answered as a blocked prompt; `/evolver-evolve` is a
Grok command. Each release tag stamps its version into the packed folder; `grok plugin update evolver-plugin` fetches it.
- **Cursor:** add this repository as a plugin marketplace and install `evolver-plugin`; it installs the repo itself.
- **Kimi Code on Windows:** close Kimi, run `node scripts/install-kimi.mjs install` from a checkout, then start a new CLI or Desktop session. Keep the Kimi CLI installed for draft creation.
`--home PATH` selects the profile root; use the same absolute `KIMI_CODE_HOME` when starting Kimi. Close Kimi before
install/update/remove. The installer stages `dist/kimi`, registers its native `kimi.plugin.json` bundle and
preserves account config, other plugins, preferences and drafts. A release bundle keeps its bytes and version;
`--version X.Y.Z` explicitly builds current sources. `remove` unregisters the plugin while retaining managed bytes.
V1 targets the main agent in local CLI and Desktop sessions on Windows with an API/BYOK provider. Commands run from the terminal:
`node <Kimi-home>/plugins/managed/evolver-plugin/bin/command.mjs kimi evolver-status`.
Other OAuth regions, web/VS Code, subagents and parallel prompts have separate acceptance gates. This local installer
does not establish support in the external Evolver CLI installer or a Marketplace.
[The Kimi adapter guide](hosts/kimi/dist/README.md) explains evidence, route identity and isolation.
- **Antigravity:** `agy plugin uninstall evolver-antigravity-plugin`, then from the `release` branch,
`git clone --depth 1 --branch release https://github.com/EvoMap/evolver-agent-plugin.git` and
`agy plugin install evolver-agent-plugin/dist/antigravity`.
Expand Down Expand Up @@ -203,6 +214,11 @@ npm run pack
claude --plugin-dir dist/claude-code
```

Packing follows each entry's module imports. The shared TOML reader vendors only the pinned
`smol-toml@1.9.1` ESM closure and its BSD-3-Clause license under `lib/vendor/`, rewriting its static import to a
relative file. Packed hosts load without an npm install; optional MCP SDK and engine packages are not copied into
those bundles. `npm ci` is required before packing source so the pinned dependency is available.

## License

MIT © EvoMap.
2 changes: 1 addition & 1 deletion bin/worker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
import { runWorker } from '../lib/channels/worker/runner.js';

const [agent, ...tasks] = process.argv.slice(2);
process.exit((await runWorker(agent, tasks)) ? 0 : 1);
process.exitCode = (await runWorker(agent, tasks)) ? 0 : 1;
40 changes: 40 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,40 @@ scripts/ pack.mjs, publish-tag.sh
a resumed session continues under the same id, and a repeated task id is one the Hub drops as a duplicate.
- **Subagents** get no recall; their checks and edits count toward the main turn.

## Indexed native sessions and isolated ACP completion

Kimi Code's profile uses wire protocol 1.5 for CLI 2.4.1 and Desktop 1.2.1 on Windows.
Both local clients use the same Kimi profile and managed plugin. It declares the native workspace/session
layout; `runtime/native-facts` owns bounded filesystem reads and refuses linked, replaced, malformed or partial
records. The driver opens a logical turn before the host admits its native turn. `app/native-turn` binds the two
using the captured byte/file boundary, then closes only the matched main-user terminal. `Stop` is an observer
hint. A detached native monitor catches terminal writes after the last hook; recovery reads those records before
orphan abort reporting. Turn number and owner guards prevent a late observer from closing a newer pending turn.
Background checks remain unknown until matching native task metadata settles them. Other hosts retain their
existing transcript/tool-result adapters; Kimi subagents and concurrent prompts are outside this v1 contract.

The optional ModelRunner `capture` port records the selected alias, provider, upstream model, effort and a
credential/config binding before recall returns. The `acp` runner revalidates that binding before creating one
private session and sending one prompt. It uses a private HOME and a selected-only TOML config; the credential is
passed in an environment variable. The private agent has no tools/subagents, project hooks, user plugins or MCP
servers. ACP success, the native/provider terminal, exact route and native process closure must agree. A separate
IPC guardian handles cancellation and owner death before removing owned scratch. On Windows the system
PowerShell helper assigns the native process to a non-inheritable kill-on-close Job Object at creation, with
only its stdio handles inherited. The helper terminates remaining job members and proves the job is empty
after native exit or stop. It reports proof separately from native output. Scratch with no proof is kept;
the job does not contain processes created by an external broker outside the descendant tree.

An unavailable/changed route is a typed preflight block, preserving the candidate without spending a model
attempt or expiring it as an abandoned draft. Ordinary model/protocol failures keep the existing retry policy.
The `kimi.com` managed route reads the current access token and device identity from the exact native profile.
It binds both to the snapshot, preserves the native provider family separately from its transport, and passes
only the access token in the private child's environment. It does not manage login or refresh.
Draft publication still uses the shared explicit command and Proxy contract. Other regions, clients and native
acceptance on each target OS are separate gates, not inferred from fixture tests.

Packing vendors only the pinned `smol-toml` ESM closure and its BSD license, rewriting that runtime import to a
relative path. Installed bundles need no npm install for TOML; source packing and release CI run `npm ci` first.

## Claude Code engine behaviour found while testing

- A `hooks.json` that lists `modules` still runs its command `hooks`.
Expand All @@ -299,6 +333,7 @@ scripts/ pack.mjs, publish-tag.sh
```bash
npm ci
npm test
npm run test:kimi
npx -p typescript tsc -p .
npm run validate
```
Expand All @@ -309,6 +344,11 @@ update. `npm run validate` packs `dist/claude-code` and runs `claude plugin vali
version: bump `version` in a host's manifest with every change meant to reach its users, or the host reports the old
version as the latest.

Release CI runs the full suite on Linux and separate Kimi packed-hook and shared runtime tests on Windows,
each on Node 22 and 26. All three jobs must pass before publication. Host tests use disposable profiles and local
service fixtures; they require no developer account, Hub token or globally installed native CLI. Windows suites
run up to four test files at once; their multi-process contention tests retain their own parallel workers.

## Open questions

- Whether `@evomap/evolver-core`'s terms allow redistributing a plugin that imports it.
17 changes: 17 additions & 0 deletions hosts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,23 @@ From each host's reviewed standalone plugin; bold is where it differs from the d

## Adding a host

Kimi Code's local adapter targets CLI 2.4.1 and Desktop 1.2.1 on Windows for the main agent in local sessions, using API/BYOK or an existing `kimi.com`
managed access token. Its native
root manifest is `kimi.plugin.json`; `scripts/install-kimi.mjs` stages a versioned package under the selected
`KIMI_CODE_HOME` and changes only its owned native registration. Native config, credentials and Evolver state
(`KIMI_CODE_HOME/evolver`) stay outside the package; unregistering retains managed bytes. Other client types,
subagents, parallel prompts and other OAuth regions require separate native acceptance.

Kimi's `UserPromptSubmit` returns `{ message }` to the model. Tool hooks observe native evidence and never promise
inline failed-check context. Stop is a hint rather than a terminal verdict: wire/task records reconcile lifecycle
and verification, with dead-owner recovery when a session has no end event. Session ids include the native-home
scope. Commands use `node <plugin>/bin/command.mjs kimi <command>` from a terminal, without prompt interception.
Distillation captures provider/model/account identity from the native session and uses a private ACP profile with
no user/project hooks, plugins, MCP or skills; missing or changed route identity keeps the candidate for retry.
Windows packed hooks have passed with a loopback fixture provider/Proxy, including native turn-zero evidence,
process ownership and normal cleanup. macOS packed acceptance and real-account acceptance are separate gates; see
[`kimi/dist/README.md`](kimi/dist/README.md).

1. `hosts/<name>/profile.js`: `export default defineProfile({ name, label, channel, events, evidence, … })` with
only the points that differ; readers that need more than a line go in sibling files.
2. `hosts/<name>/dist/`: the host's hook registration calling `node bin/hook.mjs <name> <hostEvent>` (or its MCP
Expand Down
92 changes: 92 additions & 0 deletions hosts/kimi/dist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Evolver for Kimi Code

The same package integrates Kimi Code CLI 2.4.1 and Desktop 1.2.1 on Windows.
Keep the Kimi Code CLI installed for isolated ACP draft creation. Node.js 22.13+
must be available as `node` on the hook process PATH.

The integration covers the main agent in local sessions with a resolved
API/BYOK provider. The adapter can also resolve the managed Kimi Code account on `kimi.com`. The
managed route uses an existing, fresh access token and the device identity from
the same native profile. Kimi handles sign-in and refresh. Other OAuth regions,
VS Code/web, remote sessions, parallel prompts and subagents require
separate acceptance.

The prompt hook adds recalled context. Tool hooks observe evidence; they do not add
failed-check recall mid-turn. Stop is a lifecycle hint, not proof that a turn has
completed. Native wire/task records supply terminal and verification evidence.

## Local installation

From the repository checkout or an extracted release containing this installer:

```text
node scripts/install-kimi.mjs install
node scripts/install-kimi.mjs status
node scripts/install-kimi.mjs update
node scripts/install-kimi.mjs remove
```

`--home PATH` selects the native Kimi profile root; otherwise the installer uses
`KIMI_CODE_HOME`, then `~/.kimi-code`. Set `KIMI_CODE_HOME` to the same root when
starting Kimi, using an absolute path. Relative roots have not passed acceptance;
native plugin hooks run from the package root rather than the startup directory.
Close Kimi before changing its local plugin registrations, then start
a new session to load the package. Do not install a second copy of Evolver under a
different plugin id or retain a manual duplicate hook registration.

The managed package lives at `<Kimi home>/plugins/managed/evolver-plugin`. The
installer preserves other plugin registrations, native config, credentials and
Evolver state. Removal unregisters this package while retaining its managed bytes,
matching native local-plugin removal; it does not delete preferences or drafts.
The default install/update preserves a bundled `dist/kimi` release and its version.
An explicit `--version X.Y.Z` packs the checkout at that version instead.
Packing source requires `npm ci` in that checkout; installing an existing release
bundle does not download dependencies or change the native account configuration.
The native manifest/registry contract is pinned to Kimi Code 2.4.1 and should be
retested when upgrading Kimi. This direct installation says nothing about
Marketplace eligibility or support by an external Evolver CLI installer.

## Terminal commands

Run from the project directory, using the installed package's absolute entry path.
In Desktop you can run these commands in its terminal. Use `evolver-distill` to
create a draft while the Desktop session remains open.

```text
node <Kimi-home>/plugins/managed/evolver-plugin/bin/command.mjs kimi evolver-status
node <Kimi-home>/plugins/managed/evolver-plugin/bin/command.mjs kimi evolver-drafts
node <Kimi-home>/plugins/managed/evolver-plugin/bin/command.mjs kimi evolver-distill
```

The shared command catalog provides search, login, draft publication/discard and
preferences too. Commands run directly in the terminal; chat command interception
is not part of this host's v1 contract. Publishing a draft remains an explicit user
operation. Kimi's provider account and Evolver's Proxy/Hub account are separate.

Private distillation profiles exclude user/project hooks, plugins, MCP servers and
skills. The selected credential goes to the native process through its environment.
For `kimi.com` OAuth, the private profile contains the selected provider, model and
device ID; it has no OAuth token file or refresh credential. Expired or changed
credentials keep the candidate blocked. A new verified turn can capture a fresh
binding after Kimi has renewed its access token.

On Windows, ACP completion uses a Job Object with kill-on-close. It starts the
native CLI inside the job and waits for the job to empty before deleting scratch,
including when a descendant outlives the native root. This needs Windows 10 or
later and the system Windows PowerShell to compile the bundled Win32 helper.
When proof of an empty job is lost, scratch is retained. The job contains ordinary
process descendants; it is not a sandbox for processes started by an external broker.

## Validation scope

`npm run test:kimi` covers source fixtures, packed hooks with a loopback fake Proxy,
installer rollback and route/isolation checks. CI runs those source tests on Windows
Node 22/26 and the full suite on Linux Node 22/26. They use no real credentials or
model, Proxy or Hub service. The Windows native packed main-CLI flow passed with a
loopback dummy provider/Proxy: recalled context reached the real model request,
native edits/checks produced one candidate/reuse report, the owner matched Kimi and
workers exited. Loopback results do not establish real provider entitlement.
Windows Desktop packed acceptance used the native app with a loopback provider and
Proxy. It verified Recipe injection, a passing Bash check, and one private draft
through the installed CLI's isolated ACP completion. macOS, real Desktop provider
acceptance, and other clients are separate gates.
21 changes: 21 additions & 0 deletions hosts/kimi/dist/kimi.plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "evolver-plugin",
"version": "1.0.0",
"description": "Evolver recall, evidence-backed reuse reports and verified-turn drafts for Kimi Code.",
"repository": "https://github.com/EvoMap/evolver-agent-plugin",
"license": "MIT",
"hooks": [
{ "event": "SessionStart", "command": "node bin/hook.mjs kimi SessionStart", "timeout": 3 },
{ "event": "UserPromptSubmit", "command": "node bin/hook.mjs kimi UserPromptSubmit", "timeout": 10 },
{ "event": "TurnStarted", "command": "node bin/hook.mjs kimi TurnStarted", "timeout": 3 },
{ "event": "PreToolUse", "command": "node bin/hook.mjs kimi PreToolUse", "timeout": 3 },
{ "event": "PostToolUse", "command": "node bin/hook.mjs kimi PostToolUse", "timeout": 3 },
{ "event": "PostToolUseFailure", "command": "node bin/hook.mjs kimi PostToolUseFailure", "timeout": 3 },
{ "event": "Stop", "command": "node bin/hook.mjs kimi Stop", "timeout": 3 },
{ "event": "StopFailure", "command": "node bin/hook.mjs kimi StopFailure", "timeout": 3 },
{ "event": "Interrupt", "command": "node bin/hook.mjs kimi Interrupt", "timeout": 3 },
{ "event": "SessionEnd", "command": "node bin/hook.mjs kimi SessionEnd", "timeout": 3 },
{ "event": "TaskStarted", "command": "node bin/hook.mjs kimi TaskStarted", "timeout": 3 },
{ "event": "Notification", "command": "node bin/hook.mjs kimi Notification", "timeout": 3 }
]
}
8 changes: 8 additions & 0 deletions hosts/kimi/dist/pack.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"entries": ["bin/hook.mjs", "bin/worker.mjs", "bin/command.mjs"],
"root": {
"kimi.plugin.json": "hosts/kimi/dist/kimi.plugin.json",
"README.md": "hosts/kimi/dist/README.md",
"LICENSE": "LICENSE"
}
}
22 changes: 22 additions & 0 deletions hosts/kimi/evidence.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 EvoMap

import { isAbsolute, resolve } from 'node:path';

const factsOf = context => context?.nativeFacts?.status === 'ok' && context.nativeFacts.schemaSupported ? context.nativeFacts : null;

export function editedFilesOf(payload, context = {}) {
const facts = factsOf(context);
if (payload.hook_event_name !== 'PreToolUse' || !['Write', 'Edit'].includes(payload.tool_name) || facts?.toolTurnId == null) return [];
const file = payload.tool_input?.path;
if (typeof file !== 'string' || !file || file.includes('\0')) return [];
return isAbsolute(file) ? [file] : typeof payload.cwd === 'string' && isAbsolute(payload.cwd) ? [resolve(payload.cwd, file)] : [];
}

export const kimiEvidence = Object.freeze({
// Kimi truncates hook tool_output. Only correlated native wire/task results can settle evidence.
toolNote: () => null,
editedFiles: editedFilesOf,
turnStart: (payload, context = {}) => context.nativeFacts?.mark ?? null,
turnRecord: (payload, context = {}) => factsOf(context)?.record ?? null,
});
Loading
Loading