Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/edit-diff-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"echadron": patch
---

Stop repeating the file and change count above every Edit diff. The tool header already reads `Used Edit (…/readTool.ts) · +1 -1`, and the diff below it printed `+1 -1 /full/path/to/readTool.ts` again — two extra lines on the most common tool in a session, with the full path wrapping mid-word on a narrow terminal.
5 changes: 5 additions & 0 deletions .changeset/goal-stop-wrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"echadron": patch
---

Wrap the goal panel's "no stop condition" line instead of truncating it. Every other row in that panel wraps to the panel width; this one did not, so a narrow terminal cut the sentence off mid-word.
5 changes: 5 additions & 0 deletions .changeset/read-document.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"echadron": minor
---

Added a `ReadDocument` tool. Reads Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV and PDF files as Markdown, so the agent can work with documents instead of getting raw bytes back from `Read`. Conversion runs locally through `@firecrawl/anydoc`; nothing is uploaded. An optional dependency, matching how the native terminal helpers already ship: platforms with no prebuilt binary install fine and report that the file cannot be read, rather than failing the install or crashing.
5 changes: 5 additions & 0 deletions .changeset/token-count-decimal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"echadron": patch
---

Report context windows at the size the provider states. Token counts were formatted in 1024-based units, so a 1M-token window displayed as "977k", 500k as "488k", and 200k as "195k" — every configured window under-reported by 2.4%. Tokens are not bytes, and context sizes are configured and advertised in decimal, so they are now formatted that way.
227 changes: 114 additions & 113 deletions apps/kimi-code/package.json
Original file line number Diff line number Diff line change
@@ -1,115 +1,116 @@
{
"name": "echadron",
"version": "0.31.1",
"description": "Echadron multi-provider agent harness for the terminal",
"license": "MIT",
"author": "YaseenHQ",
"homepage": "https://github.com/YaseenHQ/echadron/tree/main/apps/kimi-code#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/YaseenHQ/echadron.git",
"directory": "apps/kimi-code"
},
"bugs": {
"url": "https://github.com/YaseenHQ/echadron/issues"
},
"keywords": [
"echadron",
"cli",
"agent",
"coding-agent",
"ai",
"tui"
],
"bin": {
"echadron": "dist/main.mjs",
"chad": "dist/main.mjs",
"maker": "dist/main.mjs"
},
"files": [
"dist",
"dist-web",
"native",
"scripts/postinstall.mjs",
"README.md"
],
"type": "module",
"imports": {
"#/tui/theme": "./src/tui/theme/index.ts",
"#/tui/commands": "./src/tui/commands/index.ts",
"#/cli/sub/web": "./src/cli/sub/web/index.ts",
"#/cli/sub/web/*": "./src/cli/sub/web/*.ts",
"#/generated/vis-web-asset": [
"./src/generated/vis-web-asset.ts",
"./src/generated/vis-web-asset.d.ts"
],
"#/*": "./src/*.ts"
},
"publishConfig": {
"access": "public",
"provenance": true
},
"scripts": {
"build": "pnpm -C ../echadron-web run build && tsdown && node scripts/copy-native-assets.mjs && node scripts/copy-web-assets.mjs",
"prebuild": "node scripts/build-vis-asset.mjs",
"catalog:update": "node scripts/update-catalog.mjs --out dist/built-in-catalog.json",
"smoke": "node scripts/smoke.mjs",
"build:native:js": "node scripts/native/01-bundle.mjs",
"build:native:sea": "node scripts/native/build.mjs --profile=local",
"build:native:release": "node scripts/native/build.mjs --profile=release",
"package:native": "node scripts/native/package.mjs",
"produce:native:manifest": "node scripts/native/produce-manifest.mjs",
"release:native:resolve": "node scripts/native/resolve-release.mjs",
"test:native:smoke": "node scripts/native/smoke.mjs",
"dev": "node scripts/dev.mjs",
"dev:cli-only": "node scripts/dev-entry.mjs",
"dev:server": "node scripts/dev-entry.mjs web --no-open --debug-endpoints",
"dev:kap-server": "node scripts/dev-entry.mjs web --no-open --debug-endpoints",
"dev:kap-server:multi": "node scripts/dev-entry.mjs web --no-open --debug-endpoints",
"dev:server:restart": "node scripts/dev-server-restart.mjs",
"dev:plugin-marketplace": "node scripts/dev-plugin-marketplace-server.mjs",
"build:plugin-marketplace": "node scripts/build-plugin-marketplace-cdn.mjs",
"dev:prod": "node dist/main.mjs",
"clean": "rm -rf dist",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "pnpm -w run build:packages && vitest run",
"e2e": "pnpm -w run build:packages && KIMI_E2E=1 vitest run test/e2e",
"e2e:real": "pnpm -w run build:packages && KIMI_E2E_REAL=1 vitest run test/e2e/real-llm-smoke.e2e.test.ts",
"postinstall": "node scripts/postinstall.mjs"
},
"optionalDependencies": {
"@mariozechner/clipboard": "^0.3.9",
"node-pty": "^1.1.0"
},
"devDependencies": {
"@moonshot-ai/acp-adapter": "workspace:^",
"@moonshot-ai/acp-server": "workspace:^",
"@moonshot-ai/agent-core-v2": "workspace:^",
"@moonshot-ai/kap-server": "workspace:^",
"@moonshot-ai/kimi-code-oauth": "workspace:^",
"@moonshot-ai/kimi-code-sdk": "workspace:^",
"@moonshot-ai/kimi-telemetry": "workspace:^",
"@moonshot-ai/migration-legacy": "workspace:^",
"@moonshot-ai/pi-tui": "workspace:^",
"@moonshot-ai/vis-server": "workspace:^",
"@moonshot-ai/vis-web": "workspace:*",
"@types/semver": "^7.7.0",
"@types/yazl": "^2.4.6",
"@yaseenhq/echadron-web": "workspace:^",
"chalk": "^5.4.1",
"cli-highlight": "^2.1.11",
"commander": "^13.1.0",
"jimp": "^1.6.1",
"pathe": "^2.0.3",
"postject": "1.0.0-alpha.6",
"semver": "^7.7.4",
"smol-toml": "^1.6.1",
"tsx": "^4.21.0",
"unicode-animations": "^1.0.3",
"yazl": "^3.3.1",
"zod": "^4.3.6"
},
"engines": {
"node": ">=22.19.0"
}
"name": "echadron",
"version": "0.31.1",
"description": "Echadron multi-provider agent harness for the terminal",
"license": "MIT",
"author": "YaseenHQ",
"homepage": "https://github.com/YaseenHQ/echadron/tree/main/apps/kimi-code#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/YaseenHQ/echadron.git",
"directory": "apps/kimi-code"
},
"bugs": {
"url": "https://github.com/YaseenHQ/echadron/issues"
},
"keywords": [
"echadron",
"cli",
"agent",
"coding-agent",
"ai",
"tui"
],
"bin": {
"echadron": "dist/main.mjs",
"chad": "dist/main.mjs",
"maker": "dist/main.mjs"
},
"files": [
"dist",
"dist-web",
"native",
"scripts/postinstall.mjs",
"README.md"
],
"type": "module",
"imports": {
"#/tui/theme": "./src/tui/theme/index.ts",
"#/tui/commands": "./src/tui/commands/index.ts",
"#/cli/sub/web": "./src/cli/sub/web/index.ts",
"#/cli/sub/web/*": "./src/cli/sub/web/*.ts",
"#/generated/vis-web-asset": [
"./src/generated/vis-web-asset.ts",
"./src/generated/vis-web-asset.d.ts"
],
"#/*": "./src/*.ts"
},
"publishConfig": {
"access": "public",
"provenance": true
},
"scripts": {
"build": "pnpm -C ../echadron-web run build && tsdown && node scripts/copy-native-assets.mjs && node scripts/copy-web-assets.mjs",
"prebuild": "node scripts/build-vis-asset.mjs",
"catalog:update": "node scripts/update-catalog.mjs --out dist/built-in-catalog.json",
"smoke": "node scripts/smoke.mjs",
"build:native:js": "node scripts/native/01-bundle.mjs",
"build:native:sea": "node scripts/native/build.mjs --profile=local",
"build:native:release": "node scripts/native/build.mjs --profile=release",
"package:native": "node scripts/native/package.mjs",
"produce:native:manifest": "node scripts/native/produce-manifest.mjs",
"release:native:resolve": "node scripts/native/resolve-release.mjs",
"test:native:smoke": "node scripts/native/smoke.mjs",
"dev": "node scripts/dev.mjs",
"dev:cli-only": "node scripts/dev-entry.mjs",
"dev:server": "node scripts/dev-entry.mjs web --no-open --debug-endpoints",
"dev:kap-server": "node scripts/dev-entry.mjs web --no-open --debug-endpoints",
"dev:kap-server:multi": "node scripts/dev-entry.mjs web --no-open --debug-endpoints",
"dev:server:restart": "node scripts/dev-server-restart.mjs",
"dev:plugin-marketplace": "node scripts/dev-plugin-marketplace-server.mjs",
"build:plugin-marketplace": "node scripts/build-plugin-marketplace-cdn.mjs",
"dev:prod": "node dist/main.mjs",
"clean": "rm -rf dist",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "pnpm -w run build:packages && vitest run",
"e2e": "pnpm -w run build:packages && KIMI_E2E=1 vitest run test/e2e",
"e2e:real": "pnpm -w run build:packages && KIMI_E2E_REAL=1 vitest run test/e2e/real-llm-smoke.e2e.test.ts",
"postinstall": "node scripts/postinstall.mjs"
},
"optionalDependencies": {
"@firecrawl/anydoc": "^0.1.8",
"@mariozechner/clipboard": "^0.3.9",
"node-pty": "^1.1.0"
},
"devDependencies": {
"@moonshot-ai/acp-adapter": "workspace:^",
"@moonshot-ai/acp-server": "workspace:^",
"@moonshot-ai/agent-core-v2": "workspace:^",
"@moonshot-ai/kap-server": "workspace:^",
"@moonshot-ai/kimi-code-oauth": "workspace:^",
"@moonshot-ai/kimi-code-sdk": "workspace:^",
"@moonshot-ai/kimi-telemetry": "workspace:^",
"@moonshot-ai/migration-legacy": "workspace:^",
"@moonshot-ai/pi-tui": "workspace:^",
"@moonshot-ai/vis-server": "workspace:^",
"@moonshot-ai/vis-web": "workspace:*",
"@types/semver": "^7.7.0",
"@types/yazl": "^2.4.6",
"@yaseenhq/echadron-web": "workspace:^",
"chalk": "^5.4.1",
"cli-highlight": "^2.1.11",
"commander": "^13.1.0",
"jimp": "^1.6.1",
"pathe": "^2.0.3",
"postject": "1.0.0-alpha.6",
"semver": "^7.7.4",
"smol-toml": "^1.6.1",
"tsx": "^4.21.0",
"unicode-animations": "^1.0.3",
"yazl": "^3.3.1",
"zod": "^4.3.6"
},
"engines": {
"node": ">=22.19.0"
}
}
1 change: 1 addition & 0 deletions apps/kimi-code/scripts/native/check-bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const builtins = new Set([
]);

const optionalRuntimeRequires = new Set([
'@firecrawl/anydoc',
'ajv-formats/dist/formats',
'ajv/dist/runtime/validation_error',
'bufferutil',
Expand Down
18 changes: 13 additions & 5 deletions apps/kimi-code/src/tui/components/media/diff-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ export interface ClusteredDiffOptions {
readonly expandKeyHint?: string;
readonly oldStart?: number;
readonly newStart?: number;
/**
* Omit the `+N -M <path>` line. Set it when the caller already shows the
* file and the change stat in its own header, where repeating them costs
* two lines and wraps the full path mid-token on a narrow terminal.
*/
readonly omitHeader?: boolean;
}

interface Cluster {
Expand Down Expand Up @@ -254,11 +260,13 @@ export function renderDiffLinesClustered(
);

const output: string[] = [];
let header = '';
if (addedCount > 0) header += s.addBold(`+${String(addedCount)} `);
if (removedCount > 0) header += s.delBold(`-${String(removedCount)} `);
header += path;
output.push(header);
if (opts.omitHeader !== true) {
let header = '';
if (addedCount > 0) header += s.addBold(`+${String(addedCount)} `);
if (removedCount > 0) header += s.delBold(`-${String(removedCount)} `);
header += path;
output.push(header);
}

if (clusters.length === 0) return output;

Expand Down
18 changes: 13 additions & 5 deletions apps/kimi-code/src/tui/components/messages/goal-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,19 @@ export function buildGoalReportLines(goal: GoalSnapshot, wrapWidth: number = WRA
lines.push(row('Tokens', value(formatTokenCount(goal.tokensUsed))));
if (!isComplete) {
const stop = formatStopRow(goal);
lines.push(
stop !== null
? row('Stop', value(stop))
: muted('No stop condition — runs until evaluated complete.'),
);
if (stop !== null) {
lines.push(row('Stop', value(stop)));
} else {
// Every other row is short enough to fit; this sentence is not, and
// pushing it unwrapped overflowed any terminal narrower than it.
for (const line of wrap(
'No stop condition — runs until evaluated complete.',
wrapWidth,
MAX_OBJECTIVE_LINES,
)) {
lines.push(muted(line));
}
}
}
return lines;
}
Expand Down
3 changes: 3 additions & 0 deletions apps/kimi-code/src/tui/components/messages/tool-call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,9 @@ export class ToolCallComponent extends Container {
const filePath = str(this.toolCall.args['file_path'] ?? this.toolCall.args['path']);
const lines = renderDiffLinesClustered(oldStr, newStr, filePath, {
contextLines: 3,
// The component header already carries the elided path and the +N -M
// stat; repeating them here wrapped the full path mid-token.
omitHeader: true,
...(shouldCap ? { maxLines: COMMAND_PREVIEW_LINES } : {}),
});
for (const line of lines) {
Expand Down
16 changes: 10 additions & 6 deletions apps/kimi-code/src/utils/usage/usage-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@
*/

/**
* Format a token count in 1024-based units: context sizes are powers of
* two, so 262144 reads as "256k", not "262.1k". k values at or above
* 100 are rounded to whole numbers ("977k").
* Format a token count in 1000-based units.
*
* Tokens are not bytes, and model context windows are configured and
* advertised in decimal — 200000, 500000, 1000000. Dividing by 1024 showed a
* 1M window as "977k" and a 200k window as "195k", under-reporting every
* window by 2.4% against the number the provider states. k values at or above
* 100 are rounded to whole numbers.
*/
export function formatTokenCount(n: number): string {
if (!Number.isFinite(n) || n < 0) return '0';
if (n >= 1024 * 1024) return `${trimDecimal(n / (1024 * 1024))}M`;
if (n >= 1024) {
const k = n / 1024;
if (n >= 1_000_000) return `${trimDecimal(n / 1_000_000)}M`;
if (n >= 1_000) {
const k = n / 1_000;
return `${k >= 100 ? Math.round(k) : trimDecimal(k)}k`;
}
return String(n);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe('CacheHintDialogComponent', () => {
it('renders the title with idle duration and token count', () => {
const { lines } = renderDialog();
expect(
lines.some((l) => l.includes('This session has been idle for 26d 22h and is ~286k tokens.')),
lines.some((l) => l.includes('This session has been idle for 26d 22h and is ~293k tokens.')),
).toBe(true);
});

Expand Down
14 changes: 14 additions & 0 deletions apps/kimi-code/test/tui/components/media/diff-preview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ describe('renderDiffLines', () => {
});

describe('renderDiffLinesClustered', () => {
it('omits its own header when the caller already shows path and stat', () => {
// The tool-call header carries "Used Edit (…/foo.ts) · +1 -1", so the
// body repeating "+1 -1 /very/long/path" cost two lines and wrapped the
// full path mid-token on a narrow terminal.
const withHeader = renderDiffLinesClustered('A\nB\nC', 'A\nX\nC', 'foo.ts');
const without = renderDiffLinesClustered('A\nB\nC', 'A\nX\nC', 'foo.ts', {
omitHeader: true,
});
expect(withHeader[0]).toContain('foo.ts');
expect(without.length).toBe(withHeader.length - 1);
expect(without.join('\n')).not.toContain('foo.ts');
expect(without.join('\n')).toContain('X');
});

it('renders header with file path and counts', () => {
const out = renderDiffLinesClustered('A\nB\nC', 'A\nX\nC', 'foo.ts');
const text = stripAnsi(out[0]!);
Expand Down
Loading
Loading