dsh-loop-engine 1.0.0-rc5 → 1.0.0-rc7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/README.zh.md +4 -3
- package/lib/client.js +76 -17
- package/lib/index.js +1548 -329
- package/lib/invariant.js +16 -8
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/engine-kimi/acp/client.d.ts +76 -0
- package/lib/types/engine-kimi/acp/mapping.d.ts +44 -0
- package/lib/types/engine-kimi/acp/types.d.ts +95 -0
- package/lib/types/engine-kimi/agent.d.ts +121 -0
- package/lib/types/engine-kimi/commands.d.ts +33 -0
- package/lib/types/engine-kimi/loop.d.ts +86 -0
- package/lib/types/engine-kimi/mapping.d.ts +71 -0
- package/lib/types/engine-kimi/permission.d.ts +28 -0
- package/lib/types/engine-kimi/process.d.ts +61 -0
- package/lib/types/engine-kimi/skills.d.ts +57 -0
- package/lib/types/engine-kimi/types.d.ts +23 -0
- package/lib/types/index.d.ts +4 -2
- package/lib/types/settings.d.ts +1 -1
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -46,10 +46,10 @@ var __callDispose = (stack, error, hasError) => {
|
|
|
46
46
|
|
|
47
47
|
// src/index.ts
|
|
48
48
|
import { mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
|
|
49
|
-
import { mkdir, readFile as
|
|
49
|
+
import { mkdir, readFile as readFile5, rename, writeFile } from "node:fs/promises";
|
|
50
50
|
import { randomUUID } from "node:crypto";
|
|
51
|
-
import { dirname as
|
|
52
|
-
import
|
|
51
|
+
import { dirname as dirname5, join as join10 } from "node:path";
|
|
52
|
+
import z6 from "@deepseek-ai/schemastery";
|
|
53
53
|
import { installSettingsSection } from "@deepseek-ai/dsh-settings";
|
|
54
54
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
55
55
|
|
|
@@ -1478,8 +1478,8 @@ var AppServerClient = class _AppServerClient {
|
|
|
1478
1478
|
}
|
|
1479
1479
|
const id = this.reqId++;
|
|
1480
1480
|
const msg = { jsonrpc: "2.0", id, method, params };
|
|
1481
|
-
return new Promise((
|
|
1482
|
-
this.pending.set(id, { resolve:
|
|
1481
|
+
return new Promise((resolve5, reject) => {
|
|
1482
|
+
this.pending.set(id, { resolve: resolve5, reject });
|
|
1483
1483
|
this.process.stdin.write(JSON.stringify(msg) + "\n");
|
|
1484
1484
|
});
|
|
1485
1485
|
}
|
|
@@ -1530,7 +1530,7 @@ var AppServerThread = class _AppServerThread {
|
|
|
1530
1530
|
const { signal, params } = options;
|
|
1531
1531
|
const queue = [];
|
|
1532
1532
|
const earlyNotifications = [];
|
|
1533
|
-
let
|
|
1533
|
+
let resolve5;
|
|
1534
1534
|
let done = false;
|
|
1535
1535
|
let turnError;
|
|
1536
1536
|
let turnId;
|
|
@@ -1604,7 +1604,7 @@ var AppServerThread = class _AppServerThread {
|
|
|
1604
1604
|
}
|
|
1605
1605
|
if (event) {
|
|
1606
1606
|
queue.push(event);
|
|
1607
|
-
|
|
1607
|
+
resolve5?.();
|
|
1608
1608
|
}
|
|
1609
1609
|
};
|
|
1610
1610
|
this.client.onNotification(notificationHandler);
|
|
@@ -1630,7 +1630,7 @@ var AppServerThread = class _AppServerThread {
|
|
|
1630
1630
|
void this.client.turnInterrupt({ threadId: this.threadId, turnId }).catch(() => {
|
|
1631
1631
|
});
|
|
1632
1632
|
}
|
|
1633
|
-
|
|
1633
|
+
resolve5?.();
|
|
1634
1634
|
};
|
|
1635
1635
|
signal?.addEventListener("abort", abortHandler, { once: true });
|
|
1636
1636
|
try {
|
|
@@ -1640,9 +1640,9 @@ var AppServerThread = class _AppServerThread {
|
|
|
1640
1640
|
yield queue.shift();
|
|
1641
1641
|
} else {
|
|
1642
1642
|
await new Promise((r) => {
|
|
1643
|
-
|
|
1643
|
+
resolve5 = r;
|
|
1644
1644
|
});
|
|
1645
|
-
|
|
1645
|
+
resolve5 = void 0;
|
|
1646
1646
|
}
|
|
1647
1647
|
}
|
|
1648
1648
|
if (turnError) throw turnError;
|
|
@@ -2591,8 +2591,8 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2591
2591
|
* absent falls back to the plain node child spawn.
|
|
2592
2592
|
* @returns the connected client.
|
|
2593
2593
|
*/
|
|
2594
|
-
static create(spec,
|
|
2595
|
-
const process2 =
|
|
2594
|
+
static create(spec, spawn4) {
|
|
2595
|
+
const process2 = spawn4 === void 0 ? defaultSpawn(spec) : spawn4(spec);
|
|
2596
2596
|
return new _PiRpcClient(process2);
|
|
2597
2597
|
}
|
|
2598
2598
|
/** Register the event dispatch handler. */
|
|
@@ -2637,8 +2637,8 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2637
2637
|
if (this.disposed) throw new Error("pi RPC client is disposed");
|
|
2638
2638
|
const id = command.id ?? this.reqId++;
|
|
2639
2639
|
const wire = { ...command, id };
|
|
2640
|
-
return new Promise((
|
|
2641
|
-
this.pending.set(id, { resolve:
|
|
2640
|
+
return new Promise((resolve5, reject) => {
|
|
2641
|
+
this.pending.set(id, { resolve: resolve5, reject });
|
|
2642
2642
|
this.process.stdin.write(`${JSON.stringify(wire)}
|
|
2643
2643
|
`);
|
|
2644
2644
|
});
|
|
@@ -2655,8 +2655,8 @@ var PiRpcClient = class _PiRpcClient {
|
|
|
2655
2655
|
continue;
|
|
2656
2656
|
}
|
|
2657
2657
|
if (this.disposed) return;
|
|
2658
|
-
await new Promise((
|
|
2659
|
-
this.eventWake =
|
|
2658
|
+
await new Promise((resolve5) => {
|
|
2659
|
+
this.eventWake = resolve5;
|
|
2660
2660
|
});
|
|
2661
2661
|
this.eventWake = void 0;
|
|
2662
2662
|
}
|
|
@@ -2763,13 +2763,13 @@ function specsEqual(a, b) {
|
|
|
2763
2763
|
return a.cwd === b.cwd && a.env === b.env && a.argv.length === b.argv.length && a.argv.every((value, index) => value === b.argv[index]);
|
|
2764
2764
|
}
|
|
2765
2765
|
var PiAgent = class {
|
|
2766
|
-
constructor(loopCtx, id, options, session, config,
|
|
2766
|
+
constructor(loopCtx, id, options, session, config, spawn4, bin) {
|
|
2767
2767
|
this.loopCtx = loopCtx;
|
|
2768
2768
|
this.id = id;
|
|
2769
2769
|
this.options = options;
|
|
2770
2770
|
this.session = session;
|
|
2771
2771
|
this.config = config;
|
|
2772
|
-
this.spawn =
|
|
2772
|
+
this.spawn = spawn4;
|
|
2773
2773
|
this.bin = bin;
|
|
2774
2774
|
this.dispatch = agentEvents3(loopCtx, this);
|
|
2775
2775
|
this.inbox = new Inbox3(session, {
|
|
@@ -3316,8 +3316,8 @@ var PiAgent = class {
|
|
|
3316
3316
|
held = { content: contentOf(event.message), refs: [...chunkSeqs] };
|
|
3317
3317
|
chunkSeqs.length = 0;
|
|
3318
3318
|
}
|
|
3319
|
-
for (const
|
|
3320
|
-
this.appendToolResult(turn, step,
|
|
3319
|
+
for (const toolResult2 of event.toolResults ?? []) {
|
|
3320
|
+
this.appendToolResult(turn, step, toolResult2);
|
|
3321
3321
|
}
|
|
3322
3322
|
flushHeld(lastUsage);
|
|
3323
3323
|
finished = true;
|
|
@@ -3350,15 +3350,15 @@ var PiAgent = class {
|
|
|
3350
3350
|
}
|
|
3351
3351
|
}
|
|
3352
3352
|
/** Append one Pi tool result to the durable log as a `tool/result` message. */
|
|
3353
|
-
appendToolResult(turn, step,
|
|
3354
|
-
const text = typeof
|
|
3353
|
+
appendToolResult(turn, step, toolResult2) {
|
|
3354
|
+
const text = typeof toolResult2.content === "string" ? toolResult2.content : toolResult2.content.map((block) => block.type === "text" ? block.text : "").filter((segment) => segment !== "").join("\n\n");
|
|
3355
3355
|
this.session.append("tool/result", {
|
|
3356
3356
|
turn,
|
|
3357
3357
|
step,
|
|
3358
3358
|
message: mapToolResult({
|
|
3359
|
-
toolCallId:
|
|
3359
|
+
toolCallId: toolResult2.toolCallId,
|
|
3360
3360
|
result: { content: [{ type: "text", text }] },
|
|
3361
|
-
isError:
|
|
3361
|
+
isError: toolResult2.isError === true
|
|
3362
3362
|
})
|
|
3363
3363
|
}, { surfaceOp: "append" });
|
|
3364
3364
|
}
|
|
@@ -3640,234 +3640,1102 @@ var PiLoop = class extends Service3 {
|
|
|
3640
3640
|
}
|
|
3641
3641
|
};
|
|
3642
3642
|
|
|
3643
|
-
// src/
|
|
3643
|
+
// src/engine-kimi/loop.ts
|
|
3644
|
+
import { Service as Service4 } from "@deepseek-ai/cordis";
|
|
3644
3645
|
import z4 from "@deepseek-ai/schemastery";
|
|
3645
|
-
import {
|
|
3646
|
+
import { emitAgentEvent as emitAgentEvent4 } from "@deepseek-ai/dsh-agent";
|
|
3647
|
+
import { SessionPreparation as SessionPreparation4 } from "@deepseek-ai/dsh-session";
|
|
3646
3648
|
|
|
3647
|
-
// src/
|
|
3648
|
-
|
|
3649
|
+
// src/engine-kimi/agent.ts
|
|
3650
|
+
import { Inbox as Inbox4, agentEvents as agentEvents4 } from "@deepseek-ai/dsh-agent";
|
|
3651
|
+
import { CallId as CallId6, LlmError as LlmError4, createAssistantMessage as createAssistantMessage4, createUserMessage as createUserMessage4, errorChain as errorChain4 } from "@deepseek-ai/dsh-llm";
|
|
3652
|
+
import { createScope as createScope4 } from "@deepseek-ai/dsh-scope";
|
|
3653
|
+
import { canonicalHeader as canonicalHeader4 } from "@deepseek-ai/dsh-session";
|
|
3649
3654
|
|
|
3650
|
-
// src/
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
return settingsNamespace(LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL);
|
|
3655
|
+
// src/engine-kimi/process.ts
|
|
3656
|
+
import { existsSync } from "node:fs";
|
|
3657
|
+
import { homedir } from "node:os";
|
|
3658
|
+
import { join as join3 } from "node:path";
|
|
3659
|
+
function kimiHomeDir() {
|
|
3660
|
+
const envHome = process.env.KIMI_CODE_HOME;
|
|
3661
|
+
return envHome !== void 0 && envHome !== "" ? envHome : join3(homedir(), ".kimi-code");
|
|
3658
3662
|
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
`;
|
|
3665
|
-
function renderManagedBlock(engine) {
|
|
3666
|
-
if (engine === "in-process") return "";
|
|
3667
|
-
return [
|
|
3668
|
-
`${MANAGED_BLOCK_BEGIN}${engine} --`,
|
|
3669
|
-
"- id: agent-loop",
|
|
3670
|
-
" disabled: true",
|
|
3671
|
-
END_MARKER_LINE
|
|
3672
|
-
].join("\n");
|
|
3663
|
+
function kimiBinResolver(configBin) {
|
|
3664
|
+
if (configBin !== void 0 && configBin !== "") return configBin;
|
|
3665
|
+
const executable = process.platform === "win32" ? "kimi.exe" : "kimi";
|
|
3666
|
+
const candidate = join3(kimiHomeDir(), "bin", executable);
|
|
3667
|
+
return existsSync(candidate) ? candidate : "kimi";
|
|
3673
3668
|
}
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
const engine = BEGIN_MARKER_RE.exec(text)?.[1];
|
|
3677
|
-
return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : "in-process";
|
|
3669
|
+
function kimiAcpArgv(bin) {
|
|
3670
|
+
return [bin, "acp"];
|
|
3678
3671
|
}
|
|
3679
|
-
function
|
|
3680
|
-
const begin = text.indexOf(MANAGED_BLOCK_BEGIN);
|
|
3681
|
-
if (begin === -1) return { head: text, tail: "", present: false, blankBefore: false };
|
|
3682
|
-
const afterBegin = begin + MANAGED_BLOCK_BEGIN.length;
|
|
3683
|
-
const endAt = text.indexOf(MANAGED_BLOCK_END, afterBegin);
|
|
3684
|
-
const spanEnd = endAt === -1 ? text.length : endAt + END_MARKER_LINE.length;
|
|
3685
|
-
const before = text.slice(0, begin);
|
|
3686
|
-
const blankBefore = before.endsWith("\n\n");
|
|
3672
|
+
function kimiSubprocessSpec(spec, graceMs) {
|
|
3687
3673
|
return {
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3674
|
+
argv: [...spec.argv],
|
|
3675
|
+
cwd: spec.cwd,
|
|
3676
|
+
stdio: { stdin: "pipe", stdout: "pipe", stderr: "pipe" },
|
|
3677
|
+
graceMs,
|
|
3678
|
+
env: spec.env,
|
|
3679
|
+
...spec.signal === void 0 ? {} : { signal: spec.signal }
|
|
3692
3680
|
};
|
|
3693
3681
|
}
|
|
3694
|
-
function
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
return
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3682
|
+
function fromSubprocess2(handle) {
|
|
3683
|
+
const { stdin, stdout, stderr } = handle;
|
|
3684
|
+
if (stdin === void 0 || stdout === void 0 || stderr === void 0) {
|
|
3685
|
+
throw new Error("agent-loop-kimi: spawned child must pipe stdin/stdout/stderr");
|
|
3686
|
+
}
|
|
3687
|
+
return {
|
|
3688
|
+
stdin,
|
|
3689
|
+
stdout,
|
|
3690
|
+
stderr,
|
|
3691
|
+
done: handle.done,
|
|
3692
|
+
terminate: () => handle.terminate()
|
|
3693
|
+
};
|
|
3703
3694
|
}
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3695
|
+
|
|
3696
|
+
// src/engine-kimi/acp/client.ts
|
|
3697
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
3698
|
+
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
3699
|
+
|
|
3700
|
+
// src/engine-kimi/acp/types.ts
|
|
3701
|
+
function isUpdateFrame(frame2) {
|
|
3702
|
+
return frame2.method === "session/update" && typeof frame2.params === "object" && frame2.params !== null && typeof frame2.params.update === "object" && frame2.params.update !== null;
|
|
3709
3703
|
}
|
|
3710
|
-
function
|
|
3711
|
-
|
|
3712
|
-
const span = managedSpan(text);
|
|
3713
|
-
let result;
|
|
3714
|
-
if (!span.present) {
|
|
3715
|
-
if (block === "") return text;
|
|
3716
|
-
const base = ensureTrailingNewline(text);
|
|
3717
|
-
result = `${base}
|
|
3718
|
-
${block}`;
|
|
3719
|
-
} else if (block === "") {
|
|
3720
|
-
result = span.tail.startsWith("\n") ? `${span.head}${span.tail.slice(1)}` : `${span.head}${span.tail}`;
|
|
3721
|
-
} else {
|
|
3722
|
-
result = `${span.head}${span.blankBefore ? "\n" : ""}${block}${span.tail}`;
|
|
3723
|
-
}
|
|
3724
|
-
if (block !== "") return dropSeedPlaceholder(result);
|
|
3725
|
-
return hasRootEntry(result) ? result : seedEmptyArray(result);
|
|
3704
|
+
function isPermissionRequestFrame(frame2) {
|
|
3705
|
+
return frame2.method === "session/request_permission" && typeof frame2.id === "number";
|
|
3726
3706
|
}
|
|
3727
3707
|
|
|
3728
|
-
// src/
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
return (
|
|
3736
|
-
invocation.agent.followup(createUserMessage4({
|
|
3737
|
-
content: [{ type: "text", text: `/${name2}${invocation.rawInput}` }],
|
|
3738
|
-
source: { kind: "user" }
|
|
3739
|
-
}));
|
|
3740
|
-
return { kind: "success" };
|
|
3741
|
-
};
|
|
3708
|
+
// src/engine-kimi/acp/client.ts
|
|
3709
|
+
function defaultSpawn2(spec) {
|
|
3710
|
+
const child = spawn3(spec.argv[0], spec.argv.slice(1), {
|
|
3711
|
+
cwd: spec.cwd,
|
|
3712
|
+
env: spec.env,
|
|
3713
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3714
|
+
});
|
|
3715
|
+
return fromNativeChild(child);
|
|
3742
3716
|
}
|
|
3743
|
-
function
|
|
3744
|
-
|
|
3717
|
+
function fromNativeChild(child) {
|
|
3718
|
+
const done = Promise.withResolvers();
|
|
3719
|
+
child.once("exit", () => done.resolve(void 0));
|
|
3720
|
+
child.once("error", done.reject);
|
|
3721
|
+
return {
|
|
3722
|
+
stdin: child.stdin,
|
|
3723
|
+
stdout: child.stdout,
|
|
3724
|
+
stderr: child.stderr,
|
|
3725
|
+
done: done.promise,
|
|
3726
|
+
terminate: () => child.kill()
|
|
3727
|
+
};
|
|
3745
3728
|
}
|
|
3746
|
-
var
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3729
|
+
var AcpClient = class _AcpClient {
|
|
3730
|
+
/** Mount a client over an already-spawned `kimi acp` process. */
|
|
3731
|
+
constructor(process2) {
|
|
3732
|
+
this.process = process2;
|
|
3733
|
+
this.process.stdout.on("data", (chunk) => this.feed(chunk));
|
|
3734
|
+
this.process.stderr.on("data", () => {
|
|
3735
|
+
});
|
|
3736
|
+
this.process.done.then(() => {
|
|
3737
|
+
this.sealed = true;
|
|
3738
|
+
const error = new Error("kimi acp process exited unexpectedly");
|
|
3739
|
+
for (const { reject } of this.pending.values()) reject(error);
|
|
3740
|
+
this.pending.clear();
|
|
3741
|
+
this.updateWake?.();
|
|
3742
|
+
}, () => {
|
|
3743
|
+
this.sealed = true;
|
|
3744
|
+
this.updateWake?.();
|
|
3745
|
+
});
|
|
3761
3746
|
}
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
const description = commandDescription(raw);
|
|
3776
|
-
if (description === void 0) continue;
|
|
3777
|
-
seen.add(name2);
|
|
3778
|
-
definitions.push({ name: name2, description, handler: forwardClaudeCodeCommand(name2) });
|
|
3747
|
+
process;
|
|
3748
|
+
pending = /* @__PURE__ */ new Map();
|
|
3749
|
+
updateBuffer = [];
|
|
3750
|
+
updateWake;
|
|
3751
|
+
updateHandler;
|
|
3752
|
+
permissionHandler;
|
|
3753
|
+
sealed = false;
|
|
3754
|
+
nextId = 1;
|
|
3755
|
+
decoder = new StringDecoder2("utf8");
|
|
3756
|
+
buffer = "";
|
|
3757
|
+
/** Whether this client was sealed or its process exited. */
|
|
3758
|
+
get closed() {
|
|
3759
|
+
return this.sealed;
|
|
3779
3760
|
}
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3761
|
+
/**
|
|
3762
|
+
* Create a client, spawning the `kimi acp` child through the supplied
|
|
3763
|
+
* capability (or the default node spawn when none is given).
|
|
3764
|
+
* @param spec - the `kimi acp` argv/cwd/env the child should run with.
|
|
3765
|
+
* @param spawn - optional process-spawn capability (the subprocess seam).
|
|
3766
|
+
* @returns the connected client.
|
|
3767
|
+
*/
|
|
3768
|
+
static create(spec, spawn4) {
|
|
3769
|
+
const process2 = spawn4 === void 0 ? defaultSpawn2(spec) : spawn4(spec);
|
|
3770
|
+
return new _AcpClient(process2);
|
|
3771
|
+
}
|
|
3772
|
+
/** Register the event dispatch handler. */
|
|
3773
|
+
onUpdate(handler) {
|
|
3774
|
+
this.updateHandler = handler;
|
|
3775
|
+
}
|
|
3776
|
+
/** Register the permission-approval handler (reverse-RPC answers). */
|
|
3777
|
+
onPermission(handler) {
|
|
3778
|
+
this.permissionHandler = handler;
|
|
3779
|
+
}
|
|
3780
|
+
/** Send one request and await the correlated response. */
|
|
3781
|
+
request(method, params) {
|
|
3782
|
+
if (this.sealed) return Promise.reject(new Error("kimi acp client is sealed"));
|
|
3783
|
+
const id = this.nextId++;
|
|
3784
|
+
return new Promise((resolve5, reject) => {
|
|
3785
|
+
this.pending.set(id, { resolve: resolve5, reject });
|
|
3786
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, method, params })}
|
|
3787
|
+
`);
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
/** Send a notification (no correlated response awaited). */
|
|
3791
|
+
notify(method, params) {
|
|
3792
|
+
if (this.sealed) return;
|
|
3793
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params })}
|
|
3794
|
+
`);
|
|
3795
|
+
}
|
|
3796
|
+
/** Open the protocol handshake. */
|
|
3797
|
+
initialize() {
|
|
3798
|
+
return this.request("initialize", { protocolVersion: 1, clientCapabilities: {}, clientInfo: { name: "dsh-loop-engine", version: "1.0.0" } });
|
|
3799
|
+
}
|
|
3800
|
+
/** Start a fresh ACP session and resolve to its session id. */
|
|
3801
|
+
async newSession(cwd) {
|
|
3802
|
+
const result = await this.request("session/new", { cwd, mcpServers: [] });
|
|
3803
|
+
const sessionId = result?.sessionId;
|
|
3804
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
3805
|
+
throw new Error("kimi acp session/new returned no session id");
|
|
3797
3806
|
}
|
|
3798
|
-
|
|
3807
|
+
return sessionId;
|
|
3799
3808
|
}
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
return candidate.length > 120 ? `${candidate.slice(0, 119)}\u2026` : candidate;
|
|
3809
|
+
/** Prompt the agent in a session and resolve when the turn completes. */
|
|
3810
|
+
prompt(sessionId, text) {
|
|
3811
|
+
return this.request("session/prompt", { sessionId, prompt: [{ type: "text", text }] });
|
|
3804
3812
|
}
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
if (firstLine !== "---") return void 0;
|
|
3821
|
-
const start = firstLineEnd + 1;
|
|
3822
|
-
const closing = findClosingFrontmatter(raw, start);
|
|
3823
|
-
if (closing === void 0) return void 0;
|
|
3824
|
-
const yaml = raw.slice(start, closing.start);
|
|
3825
|
-
const data = {};
|
|
3826
|
-
const lines = yaml.split("\n");
|
|
3827
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
3828
|
-
const line = lines[index] ?? "";
|
|
3829
|
-
const trimmed = line.trim();
|
|
3830
|
-
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
3831
|
-
const colon = trimmed.indexOf(":");
|
|
3832
|
-
if (colon < 0) continue;
|
|
3833
|
-
const key = trimmed.slice(0, colon).trim();
|
|
3834
|
-
const value = trimmed.slice(colon + 1).trim();
|
|
3835
|
-
if (key.length === 0) continue;
|
|
3836
|
-
if (value === ">" || value === ">-" || value === "|" || value === "|-") {
|
|
3837
|
-
const block = [];
|
|
3838
|
-
while (index + 1 < lines.length) {
|
|
3839
|
-
const next = lines[index + 1] ?? "";
|
|
3840
|
-
if (next.trim().length > 0 && !/^[\s]/.test(next)) break;
|
|
3841
|
-
index += 1;
|
|
3842
|
-
if (next.trim().length > 0) block.push(next.trim());
|
|
3813
|
+
/** Cancel the active turn in a session (fire-and-forget). */
|
|
3814
|
+
cancel(sessionId) {
|
|
3815
|
+
this.request("session/cancel", { sessionId }).catch(() => void 0);
|
|
3816
|
+
}
|
|
3817
|
+
/** Answer a pending `session/request_permission`. */
|
|
3818
|
+
respondPermission(id, approved) {
|
|
3819
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id, result: { approved } })}
|
|
3820
|
+
`);
|
|
3821
|
+
}
|
|
3822
|
+
/** Consume every buffered update as an async generator. */
|
|
3823
|
+
async *updates() {
|
|
3824
|
+
while (true) {
|
|
3825
|
+
if (this.updateBuffer.length > 0) {
|
|
3826
|
+
yield this.updateBuffer.shift();
|
|
3827
|
+
continue;
|
|
3843
3828
|
}
|
|
3844
|
-
|
|
3845
|
-
|
|
3829
|
+
if (this.sealed) return;
|
|
3830
|
+
await new Promise((resolve5) => {
|
|
3831
|
+
this.updateWake = resolve5;
|
|
3832
|
+
});
|
|
3833
|
+
this.updateWake = void 0;
|
|
3846
3834
|
}
|
|
3847
|
-
data[key] = unquote(value);
|
|
3848
3835
|
}
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
const
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3836
|
+
/** Seal the client and request child termination. */
|
|
3837
|
+
dispose() {
|
|
3838
|
+
if (this.sealed) return;
|
|
3839
|
+
this.sealed = true;
|
|
3840
|
+
this.process.terminate();
|
|
3841
|
+
const error = new Error("kimi acp client is sealed");
|
|
3842
|
+
for (const { reject } of this.pending.values()) reject(error);
|
|
3843
|
+
this.pending.clear();
|
|
3844
|
+
this.updateWake?.();
|
|
3858
3845
|
}
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3846
|
+
feed(chunk) {
|
|
3847
|
+
if (this.sealed) return;
|
|
3848
|
+
const text = typeof chunk === "string" ? this.buffer + chunk : this.buffer + this.decoder.write(chunk);
|
|
3849
|
+
this.buffer = text;
|
|
3850
|
+
while (true) {
|
|
3851
|
+
const newline = this.buffer.indexOf("\n");
|
|
3852
|
+
if (newline === -1) break;
|
|
3853
|
+
let line = this.buffer.slice(0, newline);
|
|
3854
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
3855
|
+
if (line.endsWith("\r")) line = line.slice(0, -1);
|
|
3856
|
+
this.dispatch(line);
|
|
3869
3857
|
}
|
|
3870
|
-
|
|
3858
|
+
}
|
|
3859
|
+
/** Dispatch one parsed line: a response, an update notification, or a reverse-RPC request. */
|
|
3860
|
+
dispatch(line) {
|
|
3861
|
+
if (line.trim().length === 0) return;
|
|
3862
|
+
let frame2;
|
|
3863
|
+
try {
|
|
3864
|
+
frame2 = JSON.parse(line);
|
|
3865
|
+
} catch {
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
if (isPermissionRequestFrame(frame2)) {
|
|
3869
|
+
void this.handlePermission(frame2.id, frame2);
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3872
|
+
if (typeof frame2.id === "number" && frame2.method === void 0) {
|
|
3873
|
+
this.settle(frame2);
|
|
3874
|
+
return;
|
|
3875
|
+
}
|
|
3876
|
+
if (isUpdateFrame(frame2)) {
|
|
3877
|
+
const update = frame2.params.update;
|
|
3878
|
+
this.updateHandler?.(update);
|
|
3879
|
+
this.updateBuffer.push(update);
|
|
3880
|
+
this.updateWake?.();
|
|
3881
|
+
return;
|
|
3882
|
+
}
|
|
3883
|
+
if (typeof frame2.id === "number" && typeof frame2.method === "string") {
|
|
3884
|
+
this.process.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", id: frame2.id, error: { code: -32601, message: "Method not found" } })}
|
|
3885
|
+
`);
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
settle(frame2) {
|
|
3889
|
+
const id = frame2.id;
|
|
3890
|
+
const pending = this.pending.get(id);
|
|
3891
|
+
if (pending === void 0) return;
|
|
3892
|
+
this.pending.delete(id);
|
|
3893
|
+
if (frame2.error !== void 0) {
|
|
3894
|
+
pending.reject(new Error(frame2.error.message ?? "kimi acp request failed"));
|
|
3895
|
+
} else {
|
|
3896
|
+
pending.resolve(frame2.result);
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
async handlePermission(id, frame2) {
|
|
3900
|
+
const approved = this.permissionHandler === void 0 ? false : await this.permissionHandler(frame2);
|
|
3901
|
+
this.respondPermission(id, approved);
|
|
3902
|
+
}
|
|
3903
|
+
};
|
|
3904
|
+
|
|
3905
|
+
// src/engine-kimi/acp/mapping.ts
|
|
3906
|
+
import { CallId as CallId5, createToolResultMessage as createToolResultMessage4 } from "@deepseek-ai/dsh-llm";
|
|
3907
|
+
function isTextChunk(update) {
|
|
3908
|
+
return update.sessionUpdate === "agent_message_chunk";
|
|
3909
|
+
}
|
|
3910
|
+
function isThoughtChunk(update) {
|
|
3911
|
+
return update.sessionUpdate === "agent_thought_chunk";
|
|
3912
|
+
}
|
|
3913
|
+
function isToolCall(update) {
|
|
3914
|
+
return update.sessionUpdate === "tool_call";
|
|
3915
|
+
}
|
|
3916
|
+
function isToolCallUpdate(update) {
|
|
3917
|
+
return update.sessionUpdate === "tool_call_update";
|
|
3918
|
+
}
|
|
3919
|
+
function chunkDelta(update) {
|
|
3920
|
+
const content = update.content;
|
|
3921
|
+
if (content === void 0) return "";
|
|
3922
|
+
const text = content.text;
|
|
3923
|
+
return typeof text === "string" ? text : "";
|
|
3924
|
+
}
|
|
3925
|
+
function toolCallIdOf(update) {
|
|
3926
|
+
return update.toolCallId;
|
|
3927
|
+
}
|
|
3928
|
+
function toolCallName(update) {
|
|
3929
|
+
return update.title;
|
|
3930
|
+
}
|
|
3931
|
+
function isToolSettledStatus(status) {
|
|
3932
|
+
return status !== "pending" && status !== "queued" && status !== "running" && status !== "in_progress";
|
|
3933
|
+
}
|
|
3934
|
+
function isToolErrorStatus(status) {
|
|
3935
|
+
return status === "failed" || status === "error" || status === "denied";
|
|
3936
|
+
}
|
|
3937
|
+
function toolContentText(update) {
|
|
3938
|
+
const blocks = update.content ?? [];
|
|
3939
|
+
return blocks.map((block) => block.type === "content" && block.content.type === "text" ? block.content.text : "").join("");
|
|
3940
|
+
}
|
|
3941
|
+
function toolResult(callId, text, isError) {
|
|
3942
|
+
return createToolResultMessage4({
|
|
3943
|
+
callId: CallId5(callId),
|
|
3944
|
+
content: [{ type: "text", text: text.length > 0 ? text : "(no content)" }],
|
|
3945
|
+
isError
|
|
3946
|
+
});
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
// src/engine-kimi/permission.ts
|
|
3950
|
+
function resolveToolApproval(events) {
|
|
3951
|
+
return sessionApprovalPolicy(events) !== "ask";
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
// src/engine-kimi/agent.ts
|
|
3955
|
+
var PROVIDER4 = "kimi";
|
|
3956
|
+
var NATIVE_MODEL_LABEL4 = "kimi-native";
|
|
3957
|
+
var KimiAgent = class {
|
|
3958
|
+
constructor(loopCtx, id, options, session, config, spawn4, bin) {
|
|
3959
|
+
this.loopCtx = loopCtx;
|
|
3960
|
+
this.id = id;
|
|
3961
|
+
this.options = options;
|
|
3962
|
+
this.session = session;
|
|
3963
|
+
this.config = config;
|
|
3964
|
+
this.spawn = spawn4;
|
|
3965
|
+
this.bin = bin;
|
|
3966
|
+
this.dispatch = agentEvents4(loopCtx, this);
|
|
3967
|
+
this.inbox = new Inbox4(session, {
|
|
3968
|
+
inserted: (message) => {
|
|
3969
|
+
this.dispatch.emit("agent/inbox/inserted", { message });
|
|
3970
|
+
},
|
|
3971
|
+
discarded: (message) => {
|
|
3972
|
+
this.dispatch.emit("agent/inbox/discarded", { message });
|
|
3973
|
+
},
|
|
3974
|
+
claimed: (message, turn) => {
|
|
3975
|
+
this.dispatch.emit("agent/inbox/claimed", { message, turn });
|
|
3976
|
+
}
|
|
3977
|
+
});
|
|
3978
|
+
const lastTurn = session.events.findLast((event) => event.type === "turn/start")?.data.turn ?? 0;
|
|
3979
|
+
this.phase = { kind: "idle", lastTurn };
|
|
3980
|
+
this.scope = createScope4(loopCtx, this);
|
|
3981
|
+
this.ctx = this.scope.ctx.extend({ agent: this });
|
|
3982
|
+
this.scope.ctx.effect(() => () => {
|
|
3983
|
+
this.acp?.dispose();
|
|
3984
|
+
this.acp = void 0;
|
|
3985
|
+
}, "kimi.acpClient()");
|
|
3986
|
+
}
|
|
3987
|
+
loopCtx;
|
|
3988
|
+
id;
|
|
3989
|
+
options;
|
|
3990
|
+
session;
|
|
3991
|
+
config;
|
|
3992
|
+
spawn;
|
|
3993
|
+
bin;
|
|
3994
|
+
inbox;
|
|
3995
|
+
phase;
|
|
3996
|
+
activityDone = Promise.resolve();
|
|
3997
|
+
/** The agent-scoped registration boundary; the lifecycle owner unwinds it after the driver exits. */
|
|
3998
|
+
scope;
|
|
3999
|
+
ctx;
|
|
4000
|
+
/** Fused dispatcher, built once in the constructor so hot-path dispatches never allocate. */
|
|
4001
|
+
dispatch;
|
|
4002
|
+
/** Whether this loop instance has appended its initial/resume request anchor. */
|
|
4003
|
+
requestHeaderLogged = false;
|
|
4004
|
+
/** Lazily created ACP client, reused across steps and released on scope teardown. */
|
|
4005
|
+
acp;
|
|
4006
|
+
/** The spawn spec the cached client was built from; a change forces a respawn. */
|
|
4007
|
+
lastSpec;
|
|
4008
|
+
get status() {
|
|
4009
|
+
return this.phase.kind === "idle" || this.phase.kind === "maintenance" ? "idle" : "running";
|
|
4010
|
+
}
|
|
4011
|
+
/** Commit a phase and publish its externally visible status transition. */
|
|
4012
|
+
setPhase(next) {
|
|
4013
|
+
const previousStatus = this.status;
|
|
4014
|
+
this.phase = next;
|
|
4015
|
+
const status = this.status;
|
|
4016
|
+
if (status !== previousStatus) {
|
|
4017
|
+
this.dispatch.emit("agent/status", { status });
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
send(message, target, wakeup) {
|
|
4021
|
+
const wakingAfterAbort = wakeup && this.phase.kind !== "idle" && this.phase.abort.signal.aborted;
|
|
4022
|
+
const resolvedTarget = wakingAfterAbort ? "next-turn" : target;
|
|
4023
|
+
this.inbox.splice(resolvedTarget, Infinity, 0, [message]);
|
|
4024
|
+
if (wakeup) this.wakeDriver(wakingAfterAbort);
|
|
4025
|
+
}
|
|
4026
|
+
/**
|
|
4027
|
+
* Queue a message for the next turn and wake the driver.
|
|
4028
|
+
* @param input - the user message to deliver.
|
|
4029
|
+
*/
|
|
4030
|
+
followup(input) {
|
|
4031
|
+
this.send(input, "next-turn", true);
|
|
4032
|
+
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Queue a message for the running step and wake the driver.
|
|
4035
|
+
* @param input - the user message to deliver.
|
|
4036
|
+
*/
|
|
4037
|
+
steer(input) {
|
|
4038
|
+
this.send(input, "next-step", true);
|
|
4039
|
+
}
|
|
4040
|
+
/**
|
|
4041
|
+
* Queue a message for the running step without waking the driver.
|
|
4042
|
+
* @param input - the user message to deliver.
|
|
4043
|
+
*/
|
|
4044
|
+
inject(input) {
|
|
4045
|
+
this.send(input, "next-step", false);
|
|
4046
|
+
}
|
|
4047
|
+
cancel(cause, options = {}) {
|
|
4048
|
+
if (!options.keepInbox) {
|
|
4049
|
+
this.inbox.clear();
|
|
4050
|
+
if (this.phase.kind !== "idle") this.phase.wakeRequested = false;
|
|
4051
|
+
}
|
|
4052
|
+
if (this.phase.kind !== "idle") this.phase.abort.abort(cause);
|
|
4053
|
+
}
|
|
4054
|
+
/**
|
|
4055
|
+
* Run a maintenance job while the agent is idle.
|
|
4056
|
+
* @param job - the maintenance operation, receiving the phase abort signal.
|
|
4057
|
+
* @returns the maintenance result.
|
|
4058
|
+
*/
|
|
4059
|
+
runMaintenance(job) {
|
|
4060
|
+
if (this.phase.kind !== "idle") throw new Error(`agent "${this.id}" already has active work`);
|
|
4061
|
+
const done = Promise.withResolvers();
|
|
4062
|
+
const maintenance = {
|
|
4063
|
+
kind: "maintenance",
|
|
4064
|
+
abort: new AbortController(),
|
|
4065
|
+
lastTurn: this.phase.lastTurn,
|
|
4066
|
+
wakeRequested: false
|
|
4067
|
+
};
|
|
4068
|
+
this.setPhase(maintenance);
|
|
4069
|
+
this.activityDone = done.promise;
|
|
4070
|
+
return (async () => {
|
|
4071
|
+
try {
|
|
4072
|
+
return await job(maintenance.abort.signal);
|
|
4073
|
+
} finally {
|
|
4074
|
+
this.setPhase({ kind: "idle", lastTurn: maintenance.lastTurn });
|
|
4075
|
+
if (maintenance.wakeRequested && this.inbox.hasPending) this.wakeDriver();
|
|
4076
|
+
done.resolve();
|
|
4077
|
+
}
|
|
4078
|
+
})();
|
|
4079
|
+
}
|
|
4080
|
+
/**
|
|
4081
|
+
* Start one driver, or latch its wake behind maintenance or an aborted
|
|
4082
|
+
* activity. A wake sent while idle always opens its turn boundary, even
|
|
4083
|
+
* when its message was cleared; only a latched replay is suppressed when
|
|
4084
|
+
* the queue no longer holds the wake.
|
|
4085
|
+
* @param wakeAfterAbort - the {@link send} classification, captured before
|
|
4086
|
+
* the inbox insertion so a reentrant cancel cannot reclassify it.
|
|
4087
|
+
*/
|
|
4088
|
+
wakeDriver(wakeAfterAbort = false) {
|
|
4089
|
+
if (this.phase.kind !== "idle") {
|
|
4090
|
+
const reason = this.phase.abort.signal.reason;
|
|
4091
|
+
if (reason?.kind !== "disposed" && (this.phase.kind === "maintenance" || wakeAfterAbort)) {
|
|
4092
|
+
this.phase.wakeRequested = true;
|
|
4093
|
+
}
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
const driver = Promise.withResolvers();
|
|
4097
|
+
this.activityDone = driver.promise;
|
|
4098
|
+
this.setPhase({
|
|
4099
|
+
kind: "running",
|
|
4100
|
+
abort: new AbortController(),
|
|
4101
|
+
turn: this.phase.lastTurn,
|
|
4102
|
+
step: 0,
|
|
4103
|
+
wakeRequested: false
|
|
4104
|
+
});
|
|
4105
|
+
this.loopCtx.agents.withInitiator(this, () => this.kick()).then(driver.resolve, driver.reject);
|
|
4106
|
+
}
|
|
4107
|
+
async whenIdle() {
|
|
4108
|
+
let activity;
|
|
4109
|
+
do {
|
|
4110
|
+
await (activity = this.activityDone);
|
|
4111
|
+
} while (activity !== this.activityDone);
|
|
4112
|
+
}
|
|
4113
|
+
/** Report one failure at its live boundary, then preserve it for driver containment. */
|
|
4114
|
+
throwError(error) {
|
|
4115
|
+
const turn = this.phase.kind === "running" ? this.phase.turn : this.phase.lastTurn;
|
|
4116
|
+
const step = this.phase.kind === "running" ? this.phase.step : 0;
|
|
4117
|
+
this.dispatch.emit("agent/error", { turn, step, error });
|
|
4118
|
+
throw error;
|
|
4119
|
+
}
|
|
4120
|
+
async kick() {
|
|
4121
|
+
try {
|
|
4122
|
+
while (await this.turn()) {
|
|
4123
|
+
}
|
|
4124
|
+
} catch (_error) {
|
|
4125
|
+
} finally {
|
|
4126
|
+
if (this.phase.kind === "running") {
|
|
4127
|
+
const { turn, wakeRequested } = this.phase;
|
|
4128
|
+
this.setPhase({ kind: "idle", lastTurn: turn });
|
|
4129
|
+
if (wakeRequested && this.inbox.hasPending) this.wakeDriver();
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
async preStep(target, position) {
|
|
4134
|
+
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": pre-step outside running phase`);
|
|
4135
|
+
const signal = this.phase.abort.signal;
|
|
4136
|
+
const claimed = this.inbox.claim(target, position.turn);
|
|
4137
|
+
const decision = await this.dispatch.waterfall(
|
|
4138
|
+
"agent/pre-step",
|
|
4139
|
+
{ messages: claimed, ...position, signal },
|
|
4140
|
+
() => Promise.resolve({ kind: "enter", messages: claimed })
|
|
4141
|
+
);
|
|
4142
|
+
signal.throwIfAborted();
|
|
4143
|
+
if (decision.kind === "reject") return decision;
|
|
4144
|
+
const injected = await this.injectSkills(decision.messages, signal);
|
|
4145
|
+
signal.throwIfAborted();
|
|
4146
|
+
return injected !== decision.messages ? { kind: "enter", messages: [...injected] } : { ...decision };
|
|
4147
|
+
}
|
|
4148
|
+
/**
|
|
4149
|
+
* Scan the step's user messages for `/name` skill gestures, load each
|
|
4150
|
+
* matching skill, and inject the rendered skill content into the message
|
|
4151
|
+
* batch. This mirrors what dsh-tool-skill does for the in-process engine.
|
|
4152
|
+
* @param messages - the current step's message batch.
|
|
4153
|
+
* @param signal - cancellation signal (aborted loads are silently dropped).
|
|
4154
|
+
* @returns the original batch when no skill was invoked, or an extended
|
|
4155
|
+
* batch with injected skill-content messages appended.
|
|
4156
|
+
*/
|
|
4157
|
+
async injectSkills(messages, signal) {
|
|
4158
|
+
const names = invokedSkillNames(messages);
|
|
4159
|
+
if (names.length === 0) return messages;
|
|
4160
|
+
const skills = this.loopCtx.get("skills");
|
|
4161
|
+
if (skills === void 0) return messages;
|
|
4162
|
+
const cwd = this.session.header.cwd;
|
|
4163
|
+
const injections = [];
|
|
4164
|
+
for (const name2 of names) {
|
|
4165
|
+
if (!isSkillName(name2)) continue;
|
|
4166
|
+
let skill;
|
|
4167
|
+
try {
|
|
4168
|
+
skill = await skills.get(name2, { signal, scope: this, ...cwd === void 0 ? {} : { cwd } });
|
|
4169
|
+
} catch {
|
|
4170
|
+
continue;
|
|
4171
|
+
}
|
|
4172
|
+
if (skill === void 0 || !skill.invocation.userInvocable) continue;
|
|
4173
|
+
if (signal.aborted) return messages;
|
|
4174
|
+
injections.push(createUserMessage4({
|
|
4175
|
+
content: [{ type: "text", text: renderSkillContent(skill) }],
|
|
4176
|
+
source: { kind: "skill-invocation", name: name2, form: "instructions" }
|
|
4177
|
+
}));
|
|
4178
|
+
}
|
|
4179
|
+
return injections.length > 0 ? [...messages, ...injections] : messages;
|
|
4180
|
+
}
|
|
4181
|
+
/** Open one turn before claiming its first proposed step. */
|
|
4182
|
+
async turn() {
|
|
4183
|
+
if (this.phase.kind !== "running") {
|
|
4184
|
+
this.throwError(new Error(`agent "${this.id}": turn without driver reservation`));
|
|
4185
|
+
}
|
|
4186
|
+
const phase = this.phase;
|
|
4187
|
+
const { signal } = phase.abort;
|
|
4188
|
+
signal.throwIfAborted();
|
|
4189
|
+
const turn = phase.turn + 1;
|
|
4190
|
+
try {
|
|
4191
|
+
this.session.append("turn/start", { turn });
|
|
4192
|
+
} catch (error) {
|
|
4193
|
+
this.throwError(error);
|
|
4194
|
+
}
|
|
4195
|
+
phase.turn = turn;
|
|
4196
|
+
let turnEnds = null;
|
|
4197
|
+
let target = "next-turn";
|
|
4198
|
+
try {
|
|
4199
|
+
while (true) {
|
|
4200
|
+
signal.throwIfAborted();
|
|
4201
|
+
const step = phase.step + 1;
|
|
4202
|
+
const decision = await this.preStep(target, { turn, step });
|
|
4203
|
+
if (decision.kind === "reject") {
|
|
4204
|
+
turnEnds = { kind: "blocked" };
|
|
4205
|
+
return false;
|
|
4206
|
+
}
|
|
4207
|
+
if (turnEnds && decision.messages.length === 0) break;
|
|
4208
|
+
if (phase.step === 0 && decision.messages.length === 0) {
|
|
4209
|
+
turnEnds = { kind: "completed" };
|
|
4210
|
+
return false;
|
|
4211
|
+
}
|
|
4212
|
+
signal.throwIfAborted();
|
|
4213
|
+
this.session.append("step/start", { turn, step });
|
|
4214
|
+
phase.step = step;
|
|
4215
|
+
try {
|
|
4216
|
+
for (const message of decision.messages) {
|
|
4217
|
+
this.session.append("user/message", message, { surfaceOp: "append" });
|
|
4218
|
+
}
|
|
4219
|
+
const stepEnd = await this.step();
|
|
4220
|
+
if (turnEnds === null) turnEnds = stepEnd;
|
|
4221
|
+
} finally {
|
|
4222
|
+
this.session.append("step/end", { turn, step });
|
|
4223
|
+
}
|
|
4224
|
+
signal.throwIfAborted();
|
|
4225
|
+
if (turnEnds && this.inbox.nextStep.length === 0) {
|
|
4226
|
+
await this.dispatch.serial("agent/turn-stopping", { turn, signal });
|
|
4227
|
+
signal.throwIfAborted();
|
|
4228
|
+
}
|
|
4229
|
+
if (turnEnds && this.inbox.nextStep.length === 0) break;
|
|
4230
|
+
target = "next-step";
|
|
4231
|
+
}
|
|
4232
|
+
} catch (error) {
|
|
4233
|
+
if (signal.aborted) {
|
|
4234
|
+
turnEnds = { kind: "aborted", reason: signal.reason };
|
|
4235
|
+
throw error;
|
|
4236
|
+
}
|
|
4237
|
+
turnEnds = {
|
|
4238
|
+
kind: "error",
|
|
4239
|
+
error: error instanceof LlmError4 ? error.failure : { message: errorChain4(error), code: "UNKNOWN" }
|
|
4240
|
+
};
|
|
4241
|
+
this.throwError(error);
|
|
4242
|
+
} finally {
|
|
4243
|
+
try {
|
|
4244
|
+
this.session.append("turn/end", { turn, reason: turnEnds });
|
|
4245
|
+
} catch (error) {
|
|
4246
|
+
this.throwError(error);
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
if (!this.inbox.hasPending) return false;
|
|
4250
|
+
phase.abort = new AbortController();
|
|
4251
|
+
phase.wakeRequested = false;
|
|
4252
|
+
phase.step = 0;
|
|
4253
|
+
return true;
|
|
4254
|
+
}
|
|
4255
|
+
/** Model label recorded in the request header for one lifecycle. */
|
|
4256
|
+
modelLabel() {
|
|
4257
|
+
return this.config.model ?? NATIVE_MODEL_LABEL4;
|
|
4258
|
+
}
|
|
4259
|
+
/** Append the request header snapshot once per loop instance. */
|
|
4260
|
+
assertRequestHeader() {
|
|
4261
|
+
if (this.requestHeaderLogged) return;
|
|
4262
|
+
const header = canonicalHeader4({
|
|
4263
|
+
config: { provider: PROVIDER4, model: this.modelLabel() }
|
|
4264
|
+
});
|
|
4265
|
+
const baseline = this.session.requestHeader();
|
|
4266
|
+
this.session.append("request/header", {
|
|
4267
|
+
header,
|
|
4268
|
+
reason: baseline === void 0 ? "initial" : "resume"
|
|
4269
|
+
});
|
|
4270
|
+
this.requestHeaderLogged = true;
|
|
4271
|
+
}
|
|
4272
|
+
/** Whether two spawn specs describe the same `kimi acp` child. */
|
|
4273
|
+
specsEqual(a, b) {
|
|
4274
|
+
if (a === void 0) return false;
|
|
4275
|
+
return a.cwd === b.cwd && a.env === b.env && a.argv.length === b.argv.length && a.argv.every((value, index) => value === b.argv[index]);
|
|
4276
|
+
}
|
|
4277
|
+
/** Return the cached ACP client, respawning when the spec or process changed. */
|
|
4278
|
+
async acpClient(cwd) {
|
|
4279
|
+
const spec = this.spawnSpec(cwd);
|
|
4280
|
+
if (this.acp !== void 0 && !this.acp.closed && this.specsEqual(this.lastSpec, spec)) return this.acp;
|
|
4281
|
+
this.acp?.dispose();
|
|
4282
|
+
const client = AcpClient.create(spec, this.spawn);
|
|
4283
|
+
this.acp = client;
|
|
4284
|
+
this.lastSpec = spec;
|
|
4285
|
+
try {
|
|
4286
|
+
await client.initialize();
|
|
4287
|
+
} catch (error) {
|
|
4288
|
+
this.acp = void 0;
|
|
4289
|
+
this.lastSpec = void 0;
|
|
4290
|
+
client.dispose();
|
|
4291
|
+
throw error;
|
|
4292
|
+
}
|
|
4293
|
+
return client;
|
|
4294
|
+
}
|
|
4295
|
+
/** Build the `kimi acp` argv/cwd/env for the persistent child. */
|
|
4296
|
+
spawnSpec(cwd) {
|
|
4297
|
+
return {
|
|
4298
|
+
argv: kimiAcpArgv(this.bin),
|
|
4299
|
+
cwd,
|
|
4300
|
+
env: this.config.env
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
/** Run one `kimi acp` step for the current session history and map the streamed updates. */
|
|
4304
|
+
async step() {
|
|
4305
|
+
if (this.phase.kind !== "running") throw new Error(`agent "${this.id}": step outside running phase`);
|
|
4306
|
+
const { turn, step, abort: { signal } } = this.phase;
|
|
4307
|
+
signal.throwIfAborted();
|
|
4308
|
+
this.blocks = [];
|
|
4309
|
+
this.emittedToolCalls = /* @__PURE__ */ new Set();
|
|
4310
|
+
this.toolText = /* @__PURE__ */ new Map();
|
|
4311
|
+
const cwd = this.session.header.cwd;
|
|
4312
|
+
if (cwd === void 0 || cwd.length === 0) {
|
|
4313
|
+
throw new Error(`agent "${this.id}": no working directory \u2014 start the session with cwd metadata`);
|
|
4314
|
+
}
|
|
4315
|
+
const history = this.session.deriveMessages();
|
|
4316
|
+
const prompt = serializeHistory(history);
|
|
4317
|
+
if (prompt.length === 0) {
|
|
4318
|
+
throw new Error(`agent "${this.id}": cannot derive a prompt from an empty session log`);
|
|
4319
|
+
}
|
|
4320
|
+
this.assertRequestHeader();
|
|
4321
|
+
signal.throwIfAborted();
|
|
4322
|
+
const client = await this.acpClient(cwd);
|
|
4323
|
+
signal.throwIfAborted();
|
|
4324
|
+
client.onPermission(() => resolveToolApproval(this.session.events));
|
|
4325
|
+
const acpSessionId = await client.newSession(cwd);
|
|
4326
|
+
signal.throwIfAborted();
|
|
4327
|
+
client.onUpdate((update) => this.applyUpdate(turn, step, update));
|
|
4328
|
+
const cancel = () => {
|
|
4329
|
+
client.cancel(acpSessionId);
|
|
4330
|
+
};
|
|
4331
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
4332
|
+
try {
|
|
4333
|
+
await raceAbort(client.prompt(acpSessionId, prompt), signal, this.id);
|
|
4334
|
+
} finally {
|
|
4335
|
+
signal.removeEventListener("abort", cancel);
|
|
4336
|
+
}
|
|
4337
|
+
this.flushAssistant(turn, step);
|
|
4338
|
+
if (this.blocks.length === 0 && this.emittedToolCalls.size === 0) {
|
|
4339
|
+
throw new LlmError4(
|
|
4340
|
+
`agent "${this.id}": kimi query produced no assistant output`,
|
|
4341
|
+
"KIMI_NO_RESULT"
|
|
4342
|
+
);
|
|
4343
|
+
}
|
|
4344
|
+
return { kind: "completed" };
|
|
4345
|
+
}
|
|
4346
|
+
/** Per-step accumulation state for streamed assistant blocks and tool calls. */
|
|
4347
|
+
blocks = [];
|
|
4348
|
+
emittedToolCalls = /* @__PURE__ */ new Set();
|
|
4349
|
+
toolText = /* @__PURE__ */ new Map();
|
|
4350
|
+
blockRef(type) {
|
|
4351
|
+
return this.blocks.find((block) => block.type === type);
|
|
4352
|
+
}
|
|
4353
|
+
ensureBlock(type) {
|
|
4354
|
+
const existing = this.blockRef(type);
|
|
4355
|
+
if (existing !== void 0) return existing;
|
|
4356
|
+
const index = this.blocks.length;
|
|
4357
|
+
const block = { index, type, text: "", refs: [] };
|
|
4358
|
+
this.blocks.push(block);
|
|
4359
|
+
return block;
|
|
4360
|
+
}
|
|
4361
|
+
/** Append one streamed update's durable effect for the current step. */
|
|
4362
|
+
applyUpdate(turn, step, update) {
|
|
4363
|
+
if (isThoughtChunk(update)) {
|
|
4364
|
+
const delta = chunkDelta(update);
|
|
4365
|
+
if (delta === "") return;
|
|
4366
|
+
const block = this.ensureBlock("reasoning");
|
|
4367
|
+
const started = block.refs.length === 0;
|
|
4368
|
+
if (started) block.refs.push(this.appendChunk(turn, step, { type: "block-start", index: block.index, blockType: "reasoning" }));
|
|
4369
|
+
block.refs.push(this.appendChunk(turn, step, { type: "reasoning-delta", index: block.index, text: delta }));
|
|
4370
|
+
block.text += delta;
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
if (isTextChunk(update)) {
|
|
4374
|
+
const delta = chunkDelta(update);
|
|
4375
|
+
if (delta === "") return;
|
|
4376
|
+
const block = this.ensureBlock("text");
|
|
4377
|
+
const started = block.refs.length === 0;
|
|
4378
|
+
if (started) block.refs.push(this.appendChunk(turn, step, { type: "block-start", index: block.index, blockType: "text" }));
|
|
4379
|
+
block.refs.push(this.appendChunk(turn, step, { type: "text-delta", index: block.index, text: delta }));
|
|
4380
|
+
block.text += delta;
|
|
4381
|
+
return;
|
|
4382
|
+
}
|
|
4383
|
+
if (isToolCall(update)) {
|
|
4384
|
+
const callId = toolCallIdOf(update);
|
|
4385
|
+
if (callId === "" || this.emittedToolCalls.has(callId)) return;
|
|
4386
|
+
this.emittedToolCalls.add(callId);
|
|
4387
|
+
const name2 = toolCallName(update);
|
|
4388
|
+
this.session.append("tool/call", { turn, step, callId: CallId6(callId), name: name2, arguments: "{}" });
|
|
4389
|
+
this.toolText.set(callId, "");
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
if (isToolCallUpdate(update)) {
|
|
4393
|
+
const callId = toolCallIdOf(update);
|
|
4394
|
+
if (callId === "" || !this.toolText.has(callId)) return;
|
|
4395
|
+
const delta = toolContentText(update);
|
|
4396
|
+
const accumulated = `${this.toolText.get(callId)}${delta}`;
|
|
4397
|
+
this.toolText.set(callId, accumulated);
|
|
4398
|
+
const status = update.status;
|
|
4399
|
+
if (isToolSettledStatus(status)) {
|
|
4400
|
+
const message = toolResult(callId, accumulated, isToolErrorStatus(status));
|
|
4401
|
+
this.session.append("tool/result", { turn, step, message }, { surfaceOp: "append" });
|
|
4402
|
+
this.toolText.delete(callId);
|
|
4403
|
+
}
|
|
4404
|
+
return;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
/** Append one live chunk and return its durable seq. */
|
|
4408
|
+
appendChunk(turn, step, chunk) {
|
|
4409
|
+
return this.session.append("assistant/chunk", { turn, step, chunk }).seq;
|
|
4410
|
+
}
|
|
4411
|
+
/** Flush the accumulated assistant blocks into one durable assistant/message. */
|
|
4412
|
+
flushAssistant(turn, step) {
|
|
4413
|
+
if (this.blocks.length === 0 && this.emittedToolCalls.size === 0) return;
|
|
4414
|
+
const content = [];
|
|
4415
|
+
const refs = [];
|
|
4416
|
+
for (const block of this.blocks) {
|
|
4417
|
+
const delta = block.text;
|
|
4418
|
+
content.push(block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta });
|
|
4419
|
+
block.refs.push(this.appendChunk(turn, step, { type: "block-end", index: block.index, block: block.type === "text" ? { type: "text", text: delta } : { type: "reasoning", text: delta } }));
|
|
4420
|
+
refs.push(...block.refs);
|
|
4421
|
+
}
|
|
4422
|
+
this.session.append("assistant/message", {
|
|
4423
|
+
turn,
|
|
4424
|
+
step,
|
|
4425
|
+
message: createAssistantMessage4({
|
|
4426
|
+
content,
|
|
4427
|
+
source: { provider: PROVIDER4, model: this.modelLabel() }
|
|
4428
|
+
})
|
|
4429
|
+
}, {
|
|
4430
|
+
surfaceOp: "append",
|
|
4431
|
+
sourceEventSeqs: refs
|
|
4432
|
+
});
|
|
4433
|
+
}
|
|
4434
|
+
};
|
|
4435
|
+
|
|
4436
|
+
// src/engine-kimi/loop.ts
|
|
4437
|
+
var KIMI_DISPOSE_GRACE_MS = 3e3;
|
|
4438
|
+
var Config4 = z4.object({
|
|
4439
|
+
model: z4.string(),
|
|
4440
|
+
env: z4.dict(z4.string()).default({}),
|
|
4441
|
+
bin: z4.string()
|
|
4442
|
+
});
|
|
4443
|
+
function resolveConfig4(config) {
|
|
4444
|
+
return {
|
|
4445
|
+
model: config.model,
|
|
4446
|
+
env: config.env ?? {},
|
|
4447
|
+
bin: kimiBinResolver(config.bin)
|
|
4448
|
+
};
|
|
4449
|
+
}
|
|
4450
|
+
var KimiLoop = class extends Service4 {
|
|
4451
|
+
/** Services the loop resolves through its own fiber; blessed identically to the package-level entry inject. */
|
|
4452
|
+
static inject = ["agents", "sessions", "systemPrompt", "subprocess"];
|
|
4453
|
+
/** Validated configuration owned by the loop plugin. */
|
|
4454
|
+
config;
|
|
4455
|
+
ownership;
|
|
4456
|
+
/** Plain holder prevents Cordis from re-tracing the factory's dependency context through a caller shadow. */
|
|
4457
|
+
runtime;
|
|
4458
|
+
/** One-shot spawn capability handed to every agent, sandboxed by the subprocess seam. */
|
|
4459
|
+
spawn;
|
|
4460
|
+
constructor(ctx, config) {
|
|
4461
|
+
super(ctx, "agentLoopKimi");
|
|
4462
|
+
this.config = resolveConfig4(config);
|
|
4463
|
+
this.ownership = new FactoryOwnership(ctx.fiber);
|
|
4464
|
+
this.runtime = { ctx };
|
|
4465
|
+
this.spawn = (spec) => fromSubprocess2(this.runtime.ctx.subprocess.spawn(kimiSubprocessSpec(spec, KIMI_DISPOSE_GRACE_MS)));
|
|
4466
|
+
ctx.effect(() => () => this.ownership.dispose(), "agentLoopKimi.transactions()");
|
|
4467
|
+
ctx.effect(() => ctx.agents.setFactory(this), "agentLoopKimi.setFactory()");
|
|
4468
|
+
ctx.systemPrompt.variable("provider", (context) => context.agent?.options.provider);
|
|
4469
|
+
ctx.systemPrompt.variable("model", (context) => context.agent?.options.model);
|
|
4470
|
+
ctx.systemPrompt.variable("cwd", (context) => context.agent?.session.header.cwd);
|
|
4471
|
+
}
|
|
4472
|
+
/**
|
|
4473
|
+
* Construct the driver, scope, and one memoized reverse teardown for a new
|
|
4474
|
+
* agent. The teardown is registered with the factory and the owner fiber
|
|
4475
|
+
* BEFORE publication, so a mid-setup unload rolls everything back; `signal`
|
|
4476
|
+
* fuses caller cancellation with lifecycle teardown for setup awaits.
|
|
4477
|
+
*/
|
|
4478
|
+
/* jscpd:ignore-start -- ownership/transaction machinery mirrors the Claude Code loop factory. */
|
|
4479
|
+
prepare(ownerCtx, id, options, session, callerSignal) {
|
|
4480
|
+
ownerCtx.fiber.assertActive();
|
|
4481
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
4482
|
+
if (callerSignal?.aborted) {
|
|
4483
|
+
throw callerSignal.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal.reason });
|
|
4484
|
+
}
|
|
4485
|
+
const loopCtx = this.runtime.ctx;
|
|
4486
|
+
const abort = new AbortController();
|
|
4487
|
+
const onCallerAbort = () => {
|
|
4488
|
+
abort.abort(callerSignal?.reason instanceof Error ? callerSignal.reason : new Error(`agent "${id}" creation aborted`, { cause: callerSignal?.reason }));
|
|
4489
|
+
};
|
|
4490
|
+
const onFactoryTeardown = () => {
|
|
4491
|
+
abort.abort(this.ownership.signal.reason);
|
|
4492
|
+
};
|
|
4493
|
+
callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
4494
|
+
this.ownership.signal.addEventListener("abort", onFactoryTeardown, { once: true });
|
|
4495
|
+
let machine;
|
|
4496
|
+
let detachSession;
|
|
4497
|
+
let detachAgent;
|
|
4498
|
+
let disposing;
|
|
4499
|
+
const machineReady = Promise.withResolvers();
|
|
4500
|
+
const dispose = (ownerTriggered = false) => disposing ??= (async () => {
|
|
4501
|
+
abort.abort(new Error(`agent "${id}" lifecycle disposed`));
|
|
4502
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
4503
|
+
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
4504
|
+
try {
|
|
4505
|
+
if (machine === void 0) await machineReady.promise;
|
|
4506
|
+
if (machine !== void 0) {
|
|
4507
|
+
machine.cancel({ kind: "disposed" });
|
|
4508
|
+
await machine.whenIdle();
|
|
4509
|
+
await machine.scope.dispose();
|
|
4510
|
+
}
|
|
4511
|
+
} finally {
|
|
4512
|
+
try {
|
|
4513
|
+
detachAgent?.();
|
|
4514
|
+
detachSession?.();
|
|
4515
|
+
} finally {
|
|
4516
|
+
untrack();
|
|
4517
|
+
if (!ownerTriggered) await unfollowOwner();
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
})();
|
|
4521
|
+
const untrack = this.ownership.track(dispose);
|
|
4522
|
+
let unfollowOwner;
|
|
4523
|
+
try {
|
|
4524
|
+
unfollowOwner = ownerCtx.effect(() => () => {
|
|
4525
|
+
if (disposing !== void 0) return;
|
|
4526
|
+
abort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
4527
|
+
return dispose(true);
|
|
4528
|
+
}, `agentLoopKimi.lifecycle(${id})`);
|
|
4529
|
+
} catch (error) {
|
|
4530
|
+
untrack();
|
|
4531
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
4532
|
+
this.ownership.signal.removeEventListener("abort", onFactoryTeardown);
|
|
4533
|
+
throw error;
|
|
4534
|
+
}
|
|
4535
|
+
const assertLive = () => {
|
|
4536
|
+
if (!abort.signal.aborted) return;
|
|
4537
|
+
throw abort.signal.reason instanceof Error ? abort.signal.reason : new Error(String(abort.signal.reason));
|
|
4538
|
+
};
|
|
4539
|
+
try {
|
|
4540
|
+
const agent = machine = new KimiAgent(loopCtx, id, options, session, this.config, this.spawn, this.config.bin);
|
|
4541
|
+
machineReady.resolve();
|
|
4542
|
+
assertLive();
|
|
4543
|
+
return {
|
|
4544
|
+
agent,
|
|
4545
|
+
signal: abort.signal,
|
|
4546
|
+
publish: (source) => {
|
|
4547
|
+
assertLive();
|
|
4548
|
+
detachSession = agent.ctx.sessions.enter(session);
|
|
4549
|
+
detachAgent = loopCtx.agents.enter(agent, ownerCtx.agent);
|
|
4550
|
+
agent.ctx.sessions.announce(session);
|
|
4551
|
+
assertLive();
|
|
4552
|
+
loopCtx.agents.announce(agent);
|
|
4553
|
+
assertLive();
|
|
4554
|
+
emitAgentEvent4(loopCtx, agent, "agent/session-start", { source });
|
|
4555
|
+
assertLive();
|
|
4556
|
+
return { agent, dispose };
|
|
4557
|
+
},
|
|
4558
|
+
dispose
|
|
4559
|
+
};
|
|
4560
|
+
} catch (error) {
|
|
4561
|
+
machineReady.resolve();
|
|
4562
|
+
void dispose();
|
|
4563
|
+
throw error;
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
/** Prepare one Agent around an acquired Session, run setup, and publish it. */
|
|
4567
|
+
async setupAndPublish(ownerCtx, id, preparation, agentOptions, setup, signal, source) {
|
|
4568
|
+
var _stack = [];
|
|
4569
|
+
try {
|
|
4570
|
+
const ownedPreparation = __using(_stack, preparation);
|
|
4571
|
+
const session = ownedPreparation.session;
|
|
4572
|
+
const prepared = this.prepare(ownerCtx, id, agentOptions, session, signal);
|
|
4573
|
+
try {
|
|
4574
|
+
const setupCommit = await raceAbort(setup?.(prepared.agent.ctx), prepared.signal, id);
|
|
4575
|
+
setupCommit?.commit();
|
|
4576
|
+
return prepared.publish(source);
|
|
4577
|
+
} catch (error) {
|
|
4578
|
+
await prepared.dispose();
|
|
4579
|
+
throw error;
|
|
4580
|
+
}
|
|
4581
|
+
} catch (_) {
|
|
4582
|
+
var _error = _, _hasError = true;
|
|
4583
|
+
} finally {
|
|
4584
|
+
__callDispose(_stack, _error, _hasError);
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
/**
|
|
4588
|
+
* Create an agent and session under one caller-supplied identity, owned by
|
|
4589
|
+
* the accessing fiber.
|
|
4590
|
+
* @param ownerCtx - caller context that structurally owns the lifecycle.
|
|
4591
|
+
* @param options - identities, session seed/metadata, loop options, setup, and cancellation.
|
|
4592
|
+
* @returns the published handle.
|
|
4593
|
+
*/
|
|
4594
|
+
async createAgent(ownerCtx, options) {
|
|
4595
|
+
const preparation = SessionPreparation4.create(this.runtime.ctx.sessions.prepare(options.sessionId, {
|
|
4596
|
+
...options.seed === void 0 ? {} : { seed: options.seed },
|
|
4597
|
+
...options.meta === void 0 ? {} : { meta: options.meta }
|
|
4598
|
+
}));
|
|
4599
|
+
const published = this.setupAndPublish(
|
|
4600
|
+
ownerCtx,
|
|
4601
|
+
options.sessionId,
|
|
4602
|
+
preparation,
|
|
4603
|
+
options.agentOptions ?? {},
|
|
4604
|
+
options.setup,
|
|
4605
|
+
options.signal,
|
|
4606
|
+
"startup"
|
|
4607
|
+
);
|
|
4608
|
+
this.ownership.trackWrapper(published);
|
|
4609
|
+
return published;
|
|
4610
|
+
}
|
|
4611
|
+
/**
|
|
4612
|
+
* Resume an owned agent from the configured persistence service.
|
|
4613
|
+
* @param ownerCtx - caller context that owns load, setup, and the live lifecycle.
|
|
4614
|
+
* @param options - persisted identity, loop options, setup, and cancellation.
|
|
4615
|
+
* @returns the published handle.
|
|
4616
|
+
*/
|
|
4617
|
+
async resume(ownerCtx, options) {
|
|
4618
|
+
const persistence = this.runtime.ctx.get("sessionPersistence");
|
|
4619
|
+
if (persistence === void 0) {
|
|
4620
|
+
throw new Error("cannot resume: session persistence is not configured (load a dsh-session-persistence backend)");
|
|
4621
|
+
}
|
|
4622
|
+
return this.resumeWith(ownerCtx, persistence, options);
|
|
4623
|
+
}
|
|
4624
|
+
/** Resume through an explicit persistence handle. */
|
|
4625
|
+
async resumeWith(ownerCtx, persistence, options) {
|
|
4626
|
+
const id = options.resumeSessionId;
|
|
4627
|
+
let preparation;
|
|
4628
|
+
try {
|
|
4629
|
+
const ownerAbort = new AbortController();
|
|
4630
|
+
const unfollowOwner = ownerCtx.effect(() => () => {
|
|
4631
|
+
ownerAbort.abort(new Error(`agent "${id}" setup aborted: owner disposed during setup`));
|
|
4632
|
+
}, `agentLoopKimi.resume-load(${id})`);
|
|
4633
|
+
const fused = AbortSignal.any([
|
|
4634
|
+
...options.signal === void 0 ? [] : [options.signal],
|
|
4635
|
+
ownerAbort.signal,
|
|
4636
|
+
this.ownership.signal
|
|
4637
|
+
]);
|
|
4638
|
+
try {
|
|
4639
|
+
preparation = await raceAbortCall(
|
|
4640
|
+
() => persistence.prepare(id, fused),
|
|
4641
|
+
fused,
|
|
4642
|
+
id,
|
|
4643
|
+
(abandoned) => {
|
|
4644
|
+
abandoned[Symbol.dispose]();
|
|
4645
|
+
}
|
|
4646
|
+
);
|
|
4647
|
+
} finally {
|
|
4648
|
+
await unfollowOwner();
|
|
4649
|
+
}
|
|
4650
|
+
ownerCtx.fiber.assertActive();
|
|
4651
|
+
if (!this.ownership.isActive()) throw new Error("agent loop is not active");
|
|
4652
|
+
return await this.setupAndPublish(
|
|
4653
|
+
ownerCtx,
|
|
4654
|
+
id,
|
|
4655
|
+
preparation,
|
|
4656
|
+
options.agentOptions ?? {},
|
|
4657
|
+
options.setup,
|
|
4658
|
+
options.signal,
|
|
4659
|
+
"resume"
|
|
4660
|
+
);
|
|
4661
|
+
} finally {
|
|
4662
|
+
preparation?.[Symbol.dispose]();
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
};
|
|
4666
|
+
|
|
4667
|
+
// src/engine-kimi/skills.ts
|
|
4668
|
+
import { readdir as readdir2, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
4669
|
+
import { homedir as homedir3 } from "node:os";
|
|
4670
|
+
import { dirname as dirname3, join as join6, resolve as resolve3 } from "node:path";
|
|
4671
|
+
|
|
4672
|
+
// src/driver-core/context-files.ts
|
|
4673
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
4674
|
+
import { join as join5, resolve as resolve2 } from "node:path";
|
|
4675
|
+
|
|
4676
|
+
// src/skills.ts
|
|
4677
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
4678
|
+
import { homedir as homedir2 } from "node:os";
|
|
4679
|
+
import { join as join4, resolve } from "node:path";
|
|
4680
|
+
var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4681
|
+
var PROVIDER_NAME = "claude-code";
|
|
4682
|
+
var CLAUDE_CODE_RANK = 150;
|
|
4683
|
+
var CLAUDE_CODE_USER_RANK = 160;
|
|
4684
|
+
function parseFrontmatter(raw) {
|
|
4685
|
+
const firstLineEnd = raw.indexOf("\n");
|
|
4686
|
+
if (firstLineEnd < 0) return void 0;
|
|
4687
|
+
const firstLine = raw.slice(0, firstLineEnd).replace(/\r$/, "");
|
|
4688
|
+
if (firstLine !== "---") return void 0;
|
|
4689
|
+
const start = firstLineEnd + 1;
|
|
4690
|
+
const closing = findClosingFrontmatter(raw, start);
|
|
4691
|
+
if (closing === void 0) return void 0;
|
|
4692
|
+
const yaml = raw.slice(start, closing.start);
|
|
4693
|
+
const data = {};
|
|
4694
|
+
const lines = yaml.split("\n");
|
|
4695
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4696
|
+
const line = lines[index] ?? "";
|
|
4697
|
+
const trimmed = line.trim();
|
|
4698
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
4699
|
+
const colon = trimmed.indexOf(":");
|
|
4700
|
+
if (colon < 0) continue;
|
|
4701
|
+
const key = trimmed.slice(0, colon).trim();
|
|
4702
|
+
const value = trimmed.slice(colon + 1).trim();
|
|
4703
|
+
if (key.length === 0) continue;
|
|
4704
|
+
if (value === ">" || value === ">-" || value === "|" || value === "|-") {
|
|
4705
|
+
const block = [];
|
|
4706
|
+
while (index + 1 < lines.length) {
|
|
4707
|
+
const next = lines[index + 1] ?? "";
|
|
4708
|
+
if (next.trim().length > 0 && !/^[\s]/.test(next)) break;
|
|
4709
|
+
index += 1;
|
|
4710
|
+
if (next.trim().length > 0) block.push(next.trim());
|
|
4711
|
+
}
|
|
4712
|
+
data[key] = value.startsWith("|") ? block.join("\n") : block.join(" ");
|
|
4713
|
+
continue;
|
|
4714
|
+
}
|
|
4715
|
+
data[key] = unquote(value);
|
|
4716
|
+
}
|
|
4717
|
+
return { data, body: raw.slice(closing.bodyStart) };
|
|
4718
|
+
}
|
|
4719
|
+
function unquote(value) {
|
|
4720
|
+
if (value.length >= 2) {
|
|
4721
|
+
const first = value[0];
|
|
4722
|
+
const last = value[value.length - 1];
|
|
4723
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
4724
|
+
return value.slice(1, -1);
|
|
4725
|
+
}
|
|
4726
|
+
}
|
|
4727
|
+
return value;
|
|
4728
|
+
}
|
|
4729
|
+
function findClosingFrontmatter(raw, start) {
|
|
4730
|
+
let lineStart = start;
|
|
4731
|
+
while (lineStart <= raw.length) {
|
|
4732
|
+
const nextNewline = raw.indexOf("\n", lineStart);
|
|
4733
|
+
const lineEnd = nextNewline < 0 ? raw.length : nextNewline;
|
|
4734
|
+
const line = raw.slice(lineStart, lineEnd).replace(/\r$/, "");
|
|
4735
|
+
if (line === "---") {
|
|
4736
|
+
return { start: lineStart, bodyStart: nextNewline < 0 ? raw.length : nextNewline + 1 };
|
|
4737
|
+
}
|
|
4738
|
+
if (nextNewline < 0) return void 0;
|
|
3871
4739
|
lineStart = nextNewline + 1;
|
|
3872
4740
|
}
|
|
3873
4741
|
return void 0;
|
|
@@ -4004,94 +4872,417 @@ async function tryParseSkill(path) {
|
|
|
4004
4872
|
} catch {
|
|
4005
4873
|
return void 0;
|
|
4006
4874
|
}
|
|
4007
|
-
}
|
|
4008
|
-
async function findProjectRoot(cwd) {
|
|
4009
|
-
let current = cwd;
|
|
4010
|
-
while (true) {
|
|
4875
|
+
}
|
|
4876
|
+
async function findProjectRoot(cwd) {
|
|
4877
|
+
let current = cwd;
|
|
4878
|
+
while (true) {
|
|
4879
|
+
try {
|
|
4880
|
+
await stat(join4(current, ".git"));
|
|
4881
|
+
return current;
|
|
4882
|
+
} catch {
|
|
4883
|
+
}
|
|
4884
|
+
const parent = resolve(current, "..");
|
|
4885
|
+
if (parent === current) return cwd;
|
|
4886
|
+
current = parent;
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4890
|
+
// src/driver-core/context-files.ts
|
|
4891
|
+
async function projectAncestors(cwd) {
|
|
4892
|
+
const root = await findProjectRoot(resolve2(cwd));
|
|
4893
|
+
const dirs = [];
|
|
4894
|
+
let current = resolve2(cwd);
|
|
4895
|
+
while (true) {
|
|
4896
|
+
dirs.push(current);
|
|
4897
|
+
if (current === root) return dirs;
|
|
4898
|
+
current = resolve2(current, "..");
|
|
4899
|
+
}
|
|
4900
|
+
}
|
|
4901
|
+
async function collectProjectContextFiles(cwd, policy) {
|
|
4902
|
+
const files = [];
|
|
4903
|
+
for (const dir of await projectAncestors(cwd)) {
|
|
4904
|
+
const chosen = await dirContextFile(dir, policy);
|
|
4905
|
+
if (chosen !== void 0) files.push(chosen);
|
|
4906
|
+
}
|
|
4907
|
+
return files;
|
|
4908
|
+
}
|
|
4909
|
+
async function dirContextFile(dir, policy) {
|
|
4910
|
+
if (policy.override !== void 0) {
|
|
4911
|
+
const override = join5(dir, policy.override);
|
|
4912
|
+
if (await pathExists(override)) return override;
|
|
4913
|
+
}
|
|
4914
|
+
for (const name2 of policy.primary) {
|
|
4915
|
+
const candidate = join5(dir, name2);
|
|
4916
|
+
if (await pathExists(candidate)) return candidate;
|
|
4917
|
+
}
|
|
4918
|
+
return void 0;
|
|
4919
|
+
}
|
|
4920
|
+
async function pathExists(path) {
|
|
4921
|
+
try {
|
|
4922
|
+
await stat2(path);
|
|
4923
|
+
return true;
|
|
4924
|
+
} catch {
|
|
4925
|
+
return false;
|
|
4926
|
+
}
|
|
4927
|
+
}
|
|
4928
|
+
async function readOptionalFile(path) {
|
|
4929
|
+
try {
|
|
4930
|
+
return await readFile2(path, { encoding: "utf8" });
|
|
4931
|
+
} catch {
|
|
4932
|
+
return void 0;
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
async function anySourceNonEmpty(paths) {
|
|
4936
|
+
for (const path of paths) {
|
|
4937
|
+
const raw = await readOptionalFile(path);
|
|
4938
|
+
if (raw !== void 0 && raw.trim().length > 0) return true;
|
|
4939
|
+
}
|
|
4940
|
+
return false;
|
|
4941
|
+
}
|
|
4942
|
+
async function fileNonEmpty(path) {
|
|
4943
|
+
const raw = await readOptionalFile(path);
|
|
4944
|
+
return raw !== void 0 && raw.trim().length > 0;
|
|
4945
|
+
}
|
|
4946
|
+
async function readSources(paths) {
|
|
4947
|
+
const parts = [];
|
|
4948
|
+
for (const path of paths) {
|
|
4949
|
+
const raw = await readOptionalFile(path);
|
|
4950
|
+
if (raw !== void 0 && raw.trim().length > 0) parts.push(raw);
|
|
4951
|
+
}
|
|
4952
|
+
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
4953
|
+
}
|
|
4954
|
+
|
|
4955
|
+
// src/engine-kimi/skills.ts
|
|
4956
|
+
var PROVIDER_NAME2 = "kimi";
|
|
4957
|
+
var KIMI_AGENTS_PROJECT_RANK = 140;
|
|
4958
|
+
var KIMI_SKILL_PROJECT_RANK = 150;
|
|
4959
|
+
var KIMI_SKILL_USER_RANK = 160;
|
|
4960
|
+
var KIMI_CONTEXT_POLICY = {
|
|
4961
|
+
primary: ["AGENTS.md"]
|
|
4962
|
+
};
|
|
4963
|
+
function kimiAgentDir() {
|
|
4964
|
+
const override = process.env.KIMI_CODE_HOME;
|
|
4965
|
+
if (override !== void 0 && override.length > 0) return resolve3(override);
|
|
4966
|
+
return join6(homedir3(), ".kimi-code");
|
|
4967
|
+
}
|
|
4968
|
+
var KimiSkillProvider = class {
|
|
4969
|
+
constructor(control) {
|
|
4970
|
+
this.control = control;
|
|
4971
|
+
}
|
|
4972
|
+
control;
|
|
4973
|
+
name = PROVIDER_NAME2;
|
|
4974
|
+
async list(options) {
|
|
4975
|
+
const candidates = [];
|
|
4976
|
+
const cwd = options.cwd;
|
|
4977
|
+
if (cwd !== void 0) {
|
|
4978
|
+
const projectDirs = await projectAncestors(cwd);
|
|
4979
|
+
const contextPaths = await collectProjectContextFiles(cwd, KIMI_CONTEXT_POLICY);
|
|
4980
|
+
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, KIMI_AGENTS_PROJECT_RANK));
|
|
4981
|
+
for (const dir of projectDirs) {
|
|
4982
|
+
await this.collectSkillsDir(join6(dir, ".kimi-code", "skills"), KIMI_SKILL_PROJECT_RANK, candidates);
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
await this.collectSkillsDir(join6(kimiAgentDir(), "skills"), KIMI_SKILL_USER_RANK, candidates);
|
|
4986
|
+
if (this.control.signal.aborted) return [];
|
|
4987
|
+
return candidates;
|
|
4988
|
+
}
|
|
4989
|
+
async get(candidate, _options) {
|
|
4990
|
+
const locator = candidate.locator;
|
|
4991
|
+
if (locator.kind === "skill-file") {
|
|
4992
|
+
const parsed = await this.tryParse(locator.path);
|
|
4993
|
+
if (parsed === void 0) return void 0;
|
|
4994
|
+
return {
|
|
4995
|
+
name: parsed.name,
|
|
4996
|
+
description: parsed.description,
|
|
4997
|
+
...parsed.whenToUse === void 0 ? {} : { whenToUse: parsed.whenToUse },
|
|
4998
|
+
invocation: parsed.invocation,
|
|
4999
|
+
source: candidate.source,
|
|
5000
|
+
provider: this.name,
|
|
5001
|
+
content: parsed.content,
|
|
5002
|
+
path: locator.path,
|
|
5003
|
+
resourceBase: { kind: "directory", path: dirname3(locator.path) }
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
const content = await readSources(locator.paths);
|
|
5007
|
+
if (content === void 0) return void 0;
|
|
5008
|
+
const first = locator.paths[0];
|
|
5009
|
+
return {
|
|
5010
|
+
name: candidate.name,
|
|
5011
|
+
description: candidate.description,
|
|
5012
|
+
invocation: candidate.invocation,
|
|
5013
|
+
source: candidate.source,
|
|
5014
|
+
provider: this.name,
|
|
5015
|
+
content,
|
|
5016
|
+
path: first,
|
|
5017
|
+
resourceBase: { kind: "file", path: first }
|
|
5018
|
+
};
|
|
5019
|
+
}
|
|
5020
|
+
/** One merged `agents-md` candidate for a ranked file set. */
|
|
5021
|
+
agentsCandidate(paths, rank) {
|
|
5022
|
+
const first = paths[0];
|
|
5023
|
+
return {
|
|
5024
|
+
name: "agents-md",
|
|
5025
|
+
description: "Kimi project instructions (AGENTS.md)",
|
|
5026
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
5027
|
+
source: "custom",
|
|
5028
|
+
provider: this.name,
|
|
5029
|
+
rank,
|
|
5030
|
+
locator: { kind: "agents-md", paths },
|
|
5031
|
+
path: first,
|
|
5032
|
+
resourceBase: { kind: "file", path: first }
|
|
5033
|
+
};
|
|
5034
|
+
}
|
|
5035
|
+
/** Collect every skill in one skills directory, both kimi layouts. */
|
|
5036
|
+
async collectSkillsDir(skillsDir, rank, candidates) {
|
|
5037
|
+
let entries;
|
|
5038
|
+
try {
|
|
5039
|
+
entries = await readdir2(skillsDir, { withFileTypes: true, encoding: "utf8" });
|
|
5040
|
+
} catch {
|
|
5041
|
+
return;
|
|
5042
|
+
}
|
|
5043
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5044
|
+
const entryPath = join6(skillsDir, entry.name);
|
|
5045
|
+
const info = await stat3(entryPath).catch(() => void 0);
|
|
5046
|
+
if (info === void 0) continue;
|
|
5047
|
+
if (info.isDirectory()) {
|
|
5048
|
+
const path = join6(entryPath, "SKILL.md");
|
|
5049
|
+
const parsed2 = await this.tryParse(path);
|
|
5050
|
+
if (parsed2 === void 0) continue;
|
|
5051
|
+
candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
|
|
5052
|
+
continue;
|
|
5053
|
+
}
|
|
5054
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
5055
|
+
const parsed = await this.tryParse(entryPath);
|
|
5056
|
+
if (parsed === void 0) continue;
|
|
5057
|
+
candidates.push(this.skillCandidate(parsed, entryPath, rank, skillsDir));
|
|
5058
|
+
}
|
|
5059
|
+
}
|
|
5060
|
+
/** One parsed skill as a ranked candidate. */
|
|
5061
|
+
skillCandidate(skill, path, rank, resourceDir) {
|
|
5062
|
+
return {
|
|
5063
|
+
name: skill.name,
|
|
5064
|
+
description: skill.description,
|
|
5065
|
+
...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
|
|
5066
|
+
invocation: skill.invocation,
|
|
5067
|
+
source: "custom",
|
|
5068
|
+
provider: this.name,
|
|
5069
|
+
rank,
|
|
5070
|
+
locator: { kind: "skill-file", path },
|
|
5071
|
+
path,
|
|
5072
|
+
resourceBase: { kind: "directory", path: resourceDir }
|
|
5073
|
+
};
|
|
5074
|
+
}
|
|
5075
|
+
/** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
|
|
5076
|
+
async tryParse(path) {
|
|
4011
5077
|
try {
|
|
4012
|
-
await
|
|
4013
|
-
return
|
|
5078
|
+
const raw = await readFile3(path, { encoding: "utf8" });
|
|
5079
|
+
return parseSkillFile(raw);
|
|
4014
5080
|
} catch {
|
|
5081
|
+
return void 0;
|
|
4015
5082
|
}
|
|
4016
|
-
const parent = resolve(current, "..");
|
|
4017
|
-
if (parent === current) return cwd;
|
|
4018
|
-
current = parent;
|
|
4019
5083
|
}
|
|
5084
|
+
};
|
|
5085
|
+
|
|
5086
|
+
// src/engine-kimi/commands.ts
|
|
5087
|
+
import { createUserMessage as createUserMessage5 } from "@deepseek-ai/dsh-llm";
|
|
5088
|
+
function forwardKimiCommand(name2) {
|
|
5089
|
+
return (invocation) => {
|
|
5090
|
+
invocation.agent.followup(createUserMessage5({
|
|
5091
|
+
content: [{ type: "text", text: `/${name2}${invocation.rawInput}` }],
|
|
5092
|
+
source: { kind: "user" }
|
|
5093
|
+
}));
|
|
5094
|
+
return { kind: "success" };
|
|
5095
|
+
};
|
|
5096
|
+
}
|
|
5097
|
+
function builtin(name2, description) {
|
|
5098
|
+
return { name: name2, description, handler: forwardKimiCommand(name2) };
|
|
4020
5099
|
}
|
|
5100
|
+
var KIMI_COMMANDS = [
|
|
5101
|
+
builtin("help", "Show available Kimi Code commands"),
|
|
5102
|
+
builtin("status", "Show the current session runtime state"),
|
|
5103
|
+
builtin("compact", "Compact the conversation context to free token usage"),
|
|
5104
|
+
builtin("clear", "Start a fresh session, discarding the current context"),
|
|
5105
|
+
builtin("model", "Switch the LLM model used in the current session"),
|
|
5106
|
+
builtin("plan", "Toggle plan (read-only exploration) mode"),
|
|
5107
|
+
builtin("auto", "Toggle auto permission mode"),
|
|
5108
|
+
builtin("usage", "Show token usage, context, and quota information"),
|
|
5109
|
+
builtin("version", "Display the Kimi Code CLI version number"),
|
|
5110
|
+
builtin("goal", "Start or manage an autonomous goal")
|
|
5111
|
+
];
|
|
4021
5112
|
|
|
4022
|
-
// src/
|
|
4023
|
-
import
|
|
4024
|
-
import {
|
|
5113
|
+
// src/settings.ts
|
|
5114
|
+
import z5 from "@deepseek-ai/schemastery";
|
|
5115
|
+
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4025
5116
|
|
|
4026
|
-
// src/
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
}
|
|
5117
|
+
// src/namespace.ts
|
|
5118
|
+
var LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL = "agent-loop-engine";
|
|
5119
|
+
|
|
5120
|
+
// src/settings.ts
|
|
5121
|
+
var LOOP_ENGINE_IDS = ["in-process", "claude-code", "codex", "pi", "kimi"];
|
|
5122
|
+
var LOOP_ENGINE_SETTINGS_SCHEMA = z5.object({
|
|
5123
|
+
engine: z5.union([z5.const("in-process"), z5.const("claude-code"), z5.const("codex"), z5.const("pi"), z5.const("kimi")]).default("in-process"),
|
|
5124
|
+
showInComposer: z5.boolean().default(true)
|
|
5125
|
+
});
|
|
5126
|
+
function loopEngineSettingsNamespace() {
|
|
5127
|
+
return settingsNamespace(LOOP_ENGINE_SETTINGS_NAMESPACE_LITERAL);
|
|
4038
5128
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
5129
|
+
|
|
5130
|
+
// src/patch-manager.ts
|
|
5131
|
+
var MANAGED_BLOCK_BEGIN = "# -- dsh-loop-engine managed block: ";
|
|
5132
|
+
var MANAGED_BLOCK_END = "# -- /dsh-loop-engine managed block --";
|
|
5133
|
+
var END_MARKER_LINE = `${MANAGED_BLOCK_END}
|
|
5134
|
+
`;
|
|
5135
|
+
function renderManagedBlock(engine) {
|
|
5136
|
+
if (engine === "in-process") return "";
|
|
5137
|
+
return [
|
|
5138
|
+
`${MANAGED_BLOCK_BEGIN}${engine} --`,
|
|
5139
|
+
"- id: agent-loop",
|
|
5140
|
+
" disabled: true",
|
|
5141
|
+
END_MARKER_LINE
|
|
5142
|
+
].join("\n");
|
|
4046
5143
|
}
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
}
|
|
4052
|
-
for (const name2 of policy.primary) {
|
|
4053
|
-
const candidate = join5(dir, name2);
|
|
4054
|
-
if (await pathExists(candidate)) return candidate;
|
|
4055
|
-
}
|
|
4056
|
-
return void 0;
|
|
5144
|
+
var BEGIN_MARKER_RE = /^# -- dsh-loop-engine managed block: (\S+) --$/m;
|
|
5145
|
+
function currentEngineOf(text) {
|
|
5146
|
+
const engine = BEGIN_MARKER_RE.exec(text)?.[1];
|
|
5147
|
+
return LOOP_ENGINE_IDS.includes(engine ?? "") ? engine : "in-process";
|
|
4057
5148
|
}
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
5149
|
+
function managedSpan(text) {
|
|
5150
|
+
const begin = text.indexOf(MANAGED_BLOCK_BEGIN);
|
|
5151
|
+
if (begin === -1) return { head: text, tail: "", present: false, blankBefore: false };
|
|
5152
|
+
const afterBegin = begin + MANAGED_BLOCK_BEGIN.length;
|
|
5153
|
+
const endAt = text.indexOf(MANAGED_BLOCK_END, afterBegin);
|
|
5154
|
+
const spanEnd = endAt === -1 ? text.length : endAt + END_MARKER_LINE.length;
|
|
5155
|
+
const before = text.slice(0, begin);
|
|
5156
|
+
const blankBefore = before.endsWith("\n\n");
|
|
5157
|
+
return {
|
|
5158
|
+
head: blankBefore ? before.slice(0, -1) : before,
|
|
5159
|
+
tail: text.slice(spanEnd),
|
|
5160
|
+
present: true,
|
|
5161
|
+
blankBefore
|
|
5162
|
+
};
|
|
5163
|
+
}
|
|
5164
|
+
function ensureTrailingNewline(text) {
|
|
5165
|
+
return text.endsWith("\n") ? text : `${text}
|
|
5166
|
+
`;
|
|
5167
|
+
}
|
|
5168
|
+
function hasRootEntry(text) {
|
|
5169
|
+
return /^(?:- |\[)/m.test(text);
|
|
5170
|
+
}
|
|
5171
|
+
function dropSeedPlaceholder(text) {
|
|
5172
|
+
return text.replace(/^\[\]\n/m, "");
|
|
5173
|
+
}
|
|
5174
|
+
function seedEmptyArray(text) {
|
|
5175
|
+
const head = text.replace(/\n+$/, "");
|
|
5176
|
+
return head === "" ? "[]\n" : `${head}
|
|
5177
|
+
[]
|
|
5178
|
+
`;
|
|
5179
|
+
}
|
|
5180
|
+
function applyManagedBlock(text, engine) {
|
|
5181
|
+
const block = renderManagedBlock(engine);
|
|
5182
|
+
const span = managedSpan(text);
|
|
5183
|
+
let result;
|
|
5184
|
+
if (!span.present) {
|
|
5185
|
+
if (block === "") {
|
|
5186
|
+
result = text;
|
|
5187
|
+
} else {
|
|
5188
|
+
const base = ensureTrailingNewline(text);
|
|
5189
|
+
result = `${base}
|
|
5190
|
+
${block}`;
|
|
5191
|
+
}
|
|
5192
|
+
} else if (block === "") {
|
|
5193
|
+
result = span.tail.startsWith("\n") ? `${span.head}${span.tail.slice(1)}` : `${span.head}${span.tail}`;
|
|
5194
|
+
} else {
|
|
5195
|
+
result = `${span.head}${span.blankBefore ? "\n" : ""}${block}${span.tail}`;
|
|
4064
5196
|
}
|
|
5197
|
+
if (block !== "") return dropSeedPlaceholder(result);
|
|
5198
|
+
if (text.trim() === "") return result;
|
|
5199
|
+
return hasRootEntry(result) ? result : seedEmptyArray(result);
|
|
4065
5200
|
}
|
|
4066
|
-
|
|
5201
|
+
|
|
5202
|
+
// src/commands.ts
|
|
5203
|
+
import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
5204
|
+
import { homedir as homedir4 } from "node:os";
|
|
5205
|
+
import { join as join7 } from "node:path";
|
|
5206
|
+
import { createUserMessage as createUserMessage6 } from "@deepseek-ai/dsh-llm";
|
|
5207
|
+
var COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
|
|
5208
|
+
function forwardClaudeCodeCommand(name2) {
|
|
5209
|
+
return (invocation) => {
|
|
5210
|
+
invocation.agent.followup(createUserMessage6({
|
|
5211
|
+
content: [{ type: "text", text: `/${name2}${invocation.rawInput}` }],
|
|
5212
|
+
source: { kind: "user" }
|
|
5213
|
+
}));
|
|
5214
|
+
return { kind: "success" };
|
|
5215
|
+
};
|
|
5216
|
+
}
|
|
5217
|
+
function builtin2(name2, description) {
|
|
5218
|
+
return { name: name2, description, handler: forwardClaudeCodeCommand(name2) };
|
|
5219
|
+
}
|
|
5220
|
+
var CLAUDE_CODE_COMMANDS = [
|
|
5221
|
+
builtin2("help", "Show help about Claude Code commands"),
|
|
5222
|
+
builtin2("compact", "Compact the conversation to reduce context usage"),
|
|
5223
|
+
builtin2("clear", "Clear the conversation and start fresh"),
|
|
5224
|
+
builtin2("review", "Review recent changes (git diff)"),
|
|
5225
|
+
builtin2("explain", "Explain the selected code"),
|
|
5226
|
+
builtin2("fix", "Fix issues in the code"),
|
|
5227
|
+
builtin2("tests", "Add tests for the selected code")
|
|
5228
|
+
];
|
|
5229
|
+
function discoverUserSlashCommands() {
|
|
5230
|
+
let entries;
|
|
4067
5231
|
try {
|
|
4068
|
-
|
|
5232
|
+
entries = readdirSync(userCommandsDir(), { encoding: "utf8" });
|
|
4069
5233
|
} catch {
|
|
4070
|
-
return
|
|
5234
|
+
return [];
|
|
4071
5235
|
}
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
for (const
|
|
4075
|
-
|
|
4076
|
-
|
|
5236
|
+
const definitions = [];
|
|
5237
|
+
const seen = new Set(CLAUDE_CODE_COMMANDS.map((command) => command.name));
|
|
5238
|
+
for (const entry of entries.sort()) {
|
|
5239
|
+
if (!entry.endsWith(".md")) continue;
|
|
5240
|
+
const name2 = entry.slice(0, -".md".length);
|
|
5241
|
+
if (!COMMAND_NAME.test(name2) || seen.has(name2)) continue;
|
|
5242
|
+
const path = join7(userCommandsDir(), entry);
|
|
5243
|
+
let raw;
|
|
5244
|
+
try {
|
|
5245
|
+
raw = readFileSync2(path, "utf8");
|
|
5246
|
+
} catch {
|
|
5247
|
+
continue;
|
|
5248
|
+
}
|
|
5249
|
+
const description = commandDescription(raw);
|
|
5250
|
+
if (description === void 0) continue;
|
|
5251
|
+
seen.add(name2);
|
|
5252
|
+
definitions.push({ name: name2, description, handler: forwardClaudeCodeCommand(name2) });
|
|
4077
5253
|
}
|
|
4078
|
-
return
|
|
5254
|
+
return definitions;
|
|
4079
5255
|
}
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
return raw !== void 0 && raw.trim().length > 0;
|
|
5256
|
+
function userCommandsDir() {
|
|
5257
|
+
return join7(homedir4(), ".claude", "commands");
|
|
4083
5258
|
}
|
|
4084
|
-
|
|
4085
|
-
const
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
5259
|
+
function commandDescription(raw) {
|
|
5260
|
+
const trimmed = raw.trim();
|
|
5261
|
+
if (trimmed.length === 0) return void 0;
|
|
5262
|
+
let body = trimmed;
|
|
5263
|
+
if (trimmed.startsWith("---\n")) {
|
|
5264
|
+
const closing = trimmed.indexOf("\n---");
|
|
5265
|
+
if (closing <= 0) return void 0;
|
|
5266
|
+
for (const line of trimmed.slice(4, closing).split("\n")) {
|
|
5267
|
+
const colon = line.indexOf(":");
|
|
5268
|
+
if (colon < 0 || line.slice(0, colon).trim() !== "description") continue;
|
|
5269
|
+
const value = line.slice(colon + 1).trim().replace(/^["']|["']$/g, "");
|
|
5270
|
+
if (value.length > 0) return value;
|
|
5271
|
+
}
|
|
5272
|
+
body = trimmed.slice(closing + 4);
|
|
4089
5273
|
}
|
|
4090
|
-
|
|
5274
|
+
for (const line of body.split("\n")) {
|
|
5275
|
+
const candidate = line.trim();
|
|
5276
|
+
if (candidate.length === 0 || candidate.startsWith("#")) continue;
|
|
5277
|
+
return candidate.length > 120 ? `${candidate.slice(0, 119)}\u2026` : candidate;
|
|
5278
|
+
}
|
|
5279
|
+
return void 0;
|
|
4091
5280
|
}
|
|
4092
5281
|
|
|
4093
5282
|
// src/engine-codex/skills.ts
|
|
4094
|
-
|
|
5283
|
+
import { homedir as homedir5 } from "node:os";
|
|
5284
|
+
import { join as join8 } from "node:path";
|
|
5285
|
+
var PROVIDER_NAME3 = "codex";
|
|
4095
5286
|
var CODEX_PROJECT_RANK = 140;
|
|
4096
5287
|
var CODEX_USER_RANK = 160;
|
|
4097
5288
|
var CODEX_CONTEXT_POLICY = { primary: ["AGENTS.md"] };
|
|
@@ -4100,7 +5291,7 @@ var CodexSkillProvider = class {
|
|
|
4100
5291
|
this.control = control;
|
|
4101
5292
|
}
|
|
4102
5293
|
control;
|
|
4103
|
-
name =
|
|
5294
|
+
name = PROVIDER_NAME3;
|
|
4104
5295
|
async list(options) {
|
|
4105
5296
|
const candidates = [];
|
|
4106
5297
|
const cwd = options.cwd;
|
|
@@ -4108,7 +5299,7 @@ var CodexSkillProvider = class {
|
|
|
4108
5299
|
const paths = await collectProjectContextFiles(cwd, CODEX_CONTEXT_POLICY);
|
|
4109
5300
|
if (await anySourceNonEmpty(paths)) candidates.push(this.agentsCandidate(paths, CODEX_PROJECT_RANK));
|
|
4110
5301
|
}
|
|
4111
|
-
const userPath =
|
|
5302
|
+
const userPath = join8(homedir5(), ".codex", "AGENTS.md");
|
|
4112
5303
|
if (await fileNonEmpty(userPath)) candidates.push(this.agentsCandidate([userPath], CODEX_USER_RANK));
|
|
4113
5304
|
if (this.control.signal.aborted) return [];
|
|
4114
5305
|
return candidates;
|
|
@@ -4147,10 +5338,10 @@ var CodexSkillProvider = class {
|
|
|
4147
5338
|
};
|
|
4148
5339
|
|
|
4149
5340
|
// src/engine-pi/skills.ts
|
|
4150
|
-
import { readdir as
|
|
4151
|
-
import { homedir as
|
|
4152
|
-
import { dirname as
|
|
4153
|
-
var
|
|
5341
|
+
import { readdir as readdir3, readFile as readFile4, stat as stat4 } from "node:fs/promises";
|
|
5342
|
+
import { homedir as homedir6 } from "node:os";
|
|
5343
|
+
import { dirname as dirname4, join as join9, resolve as resolve4 } from "node:path";
|
|
5344
|
+
var PROVIDER_NAME4 = "pi";
|
|
4154
5345
|
var PI_AGENTS_PROJECT_RANK = 140;
|
|
4155
5346
|
var PI_SKILL_PROJECT_RANK = 150;
|
|
4156
5347
|
var PI_AGENTS_USER_RANK = 160;
|
|
@@ -4161,15 +5352,15 @@ var PI_CONTEXT_POLICY = {
|
|
|
4161
5352
|
};
|
|
4162
5353
|
function piAgentDir() {
|
|
4163
5354
|
const override = process.env.PI_CODING_AGENT_DIR;
|
|
4164
|
-
if (override !== void 0 && override.length > 0) return
|
|
4165
|
-
return
|
|
5355
|
+
if (override !== void 0 && override.length > 0) return resolve4(override);
|
|
5356
|
+
return join9(homedir6(), ".pi", "agent");
|
|
4166
5357
|
}
|
|
4167
5358
|
var PiSkillProvider = class {
|
|
4168
5359
|
constructor(control) {
|
|
4169
5360
|
this.control = control;
|
|
4170
5361
|
}
|
|
4171
5362
|
control;
|
|
4172
|
-
name =
|
|
5363
|
+
name = PROVIDER_NAME4;
|
|
4173
5364
|
async list(options) {
|
|
4174
5365
|
const candidates = [];
|
|
4175
5366
|
const cwd = options.cwd;
|
|
@@ -4178,13 +5369,13 @@ var PiSkillProvider = class {
|
|
|
4178
5369
|
const contextPaths = await collectProjectContextFiles(cwd, PI_CONTEXT_POLICY);
|
|
4179
5370
|
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, PI_AGENTS_PROJECT_RANK));
|
|
4180
5371
|
for (const dir of projectDirs) {
|
|
4181
|
-
await this.collectSkillsDir(
|
|
5372
|
+
await this.collectSkillsDir(join9(dir, ".pi", "skills"), PI_SKILL_PROJECT_RANK, candidates);
|
|
4182
5373
|
}
|
|
4183
5374
|
}
|
|
4184
5375
|
const userAgentDir = piAgentDir();
|
|
4185
|
-
const userContext =
|
|
5376
|
+
const userContext = join9(userAgentDir, "AGENTS.md");
|
|
4186
5377
|
if (await fileNonEmpty(userContext)) candidates.push(this.agentsCandidate([userContext], PI_AGENTS_USER_RANK));
|
|
4187
|
-
await this.collectSkillsDir(
|
|
5378
|
+
await this.collectSkillsDir(join9(userAgentDir, "skills"), PI_SKILL_USER_RANK, candidates);
|
|
4188
5379
|
if (this.control.signal.aborted) return [];
|
|
4189
5380
|
return candidates;
|
|
4190
5381
|
}
|
|
@@ -4202,7 +5393,7 @@ var PiSkillProvider = class {
|
|
|
4202
5393
|
provider: this.name,
|
|
4203
5394
|
content: parsed.content,
|
|
4204
5395
|
path: locator.path,
|
|
4205
|
-
resourceBase: { kind: "directory", path:
|
|
5396
|
+
resourceBase: { kind: "directory", path: dirname4(locator.path) }
|
|
4206
5397
|
};
|
|
4207
5398
|
}
|
|
4208
5399
|
const content = await readSources(locator.paths);
|
|
@@ -4238,16 +5429,16 @@ var PiSkillProvider = class {
|
|
|
4238
5429
|
async collectSkillsDir(skillsDir, rank, candidates) {
|
|
4239
5430
|
let entries;
|
|
4240
5431
|
try {
|
|
4241
|
-
entries = await
|
|
5432
|
+
entries = await readdir3(skillsDir, { withFileTypes: true, encoding: "utf8" });
|
|
4242
5433
|
} catch {
|
|
4243
5434
|
return;
|
|
4244
5435
|
}
|
|
4245
5436
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4246
|
-
const entryPath =
|
|
4247
|
-
const info = await
|
|
5437
|
+
const entryPath = join9(skillsDir, entry.name);
|
|
5438
|
+
const info = await stat4(entryPath).catch(() => void 0);
|
|
4248
5439
|
if (info === void 0) continue;
|
|
4249
5440
|
if (info.isDirectory()) {
|
|
4250
|
-
const path =
|
|
5441
|
+
const path = join9(entryPath, "SKILL.md");
|
|
4251
5442
|
const parsed2 = await this.tryParse(path);
|
|
4252
5443
|
if (parsed2 === void 0) continue;
|
|
4253
5444
|
candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
|
|
@@ -4277,7 +5468,7 @@ var PiSkillProvider = class {
|
|
|
4277
5468
|
/** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
|
|
4278
5469
|
async tryParse(path) {
|
|
4279
5470
|
try {
|
|
4280
|
-
const raw = await
|
|
5471
|
+
const raw = await readFile4(path, { encoding: "utf8" });
|
|
4281
5472
|
return parseSkillFile(raw);
|
|
4282
5473
|
} catch {
|
|
4283
5474
|
return void 0;
|
|
@@ -4290,23 +5481,24 @@ var name = "loop-engine";
|
|
|
4290
5481
|
var inject = [];
|
|
4291
5482
|
var MAX_MOUNT_ATTEMPTS = 40;
|
|
4292
5483
|
var MOUNT_RETRY_MS = 50;
|
|
4293
|
-
var
|
|
4294
|
-
profile:
|
|
4295
|
-
patchFilename:
|
|
4296
|
-
patchPath:
|
|
4297
|
-
permissionMode:
|
|
4298
|
-
env:
|
|
4299
|
-
model:
|
|
4300
|
-
disposeGraceMs:
|
|
4301
|
-
maxTurns:
|
|
4302
|
-
sandboxMode:
|
|
4303
|
-
approvalPolicy:
|
|
4304
|
-
piProvider:
|
|
4305
|
-
piThinking:
|
|
5484
|
+
var Config5 = z6.object({
|
|
5485
|
+
profile: z6.string(),
|
|
5486
|
+
patchFilename: z6.string(),
|
|
5487
|
+
patchPath: z6.string(),
|
|
5488
|
+
permissionMode: z6.union(CLAUDE_CODE_PERMISSION_MODES.map((mode) => z6.const(mode))),
|
|
5489
|
+
env: z6.dict(z6.string()),
|
|
5490
|
+
model: z6.string(),
|
|
5491
|
+
disposeGraceMs: z6.number(),
|
|
5492
|
+
maxTurns: z6.number(),
|
|
5493
|
+
sandboxMode: z6.union(CODEX_SANDBOX_MODES.map((mode) => z6.const(mode))),
|
|
5494
|
+
approvalPolicy: z6.union(CODEX_APPROVAL_POLICIES.map((policy) => z6.const(policy))),
|
|
5495
|
+
piProvider: z6.string(),
|
|
5496
|
+
piThinking: z6.string(),
|
|
5497
|
+
kimiBin: z6.string()
|
|
4306
5498
|
});
|
|
4307
5499
|
function resolvePatchPath(config) {
|
|
4308
5500
|
if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
|
|
4309
|
-
return
|
|
5501
|
+
return join10(
|
|
4310
5502
|
resolveDshHome(),
|
|
4311
5503
|
"profiles",
|
|
4312
5504
|
config.profile ?? "web",
|
|
@@ -4318,20 +5510,20 @@ function isMissing(error) {
|
|
|
4318
5510
|
}
|
|
4319
5511
|
async function readPatchOrUndefined(path) {
|
|
4320
5512
|
try {
|
|
4321
|
-
return await
|
|
5513
|
+
return await readFile5(path, "utf8");
|
|
4322
5514
|
} catch (error) {
|
|
4323
5515
|
if (isMissing(error)) return void 0;
|
|
4324
5516
|
throw error;
|
|
4325
5517
|
}
|
|
4326
5518
|
}
|
|
4327
5519
|
async function writePatchFile(path, text) {
|
|
4328
|
-
await mkdir(
|
|
5520
|
+
await mkdir(dirname5(path), { recursive: true });
|
|
4329
5521
|
const tmp = `${path}.tmp-${randomUUID()}`;
|
|
4330
5522
|
await writeFile(tmp, text, "utf8");
|
|
4331
5523
|
await rename(tmp, path);
|
|
4332
5524
|
}
|
|
4333
5525
|
function writePatchFileSync(path, text) {
|
|
4334
|
-
mkdirSync(
|
|
5526
|
+
mkdirSync(dirname5(path), { recursive: true });
|
|
4335
5527
|
const tmp = `${path}.tmp-${randomUUID()}`;
|
|
4336
5528
|
writeFileSync(tmp, text, "utf8");
|
|
4337
5529
|
renameSync(tmp, path);
|
|
@@ -4377,6 +5569,13 @@ function piConfig(config) {
|
|
|
4377
5569
|
...config.sandboxMode === void 0 ? {} : { sandboxMode: config.sandboxMode }
|
|
4378
5570
|
};
|
|
4379
5571
|
}
|
|
5572
|
+
function kimiConfig(config) {
|
|
5573
|
+
return {
|
|
5574
|
+
...config.model === void 0 ? {} : { model: config.model },
|
|
5575
|
+
...config.env === void 0 ? {} : { env: config.env },
|
|
5576
|
+
...config.kimiBin === void 0 ? {} : { bin: config.kimiBin }
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
4380
5579
|
function apply(ctx, config) {
|
|
4381
5580
|
const patchPath = resolvePatchPath(config);
|
|
4382
5581
|
let fileEngine = currentEngineOf(readPatchFileSync(patchPath));
|
|
@@ -4455,10 +5654,30 @@ function apply(ctx, config) {
|
|
|
4455
5654
|
}
|
|
4456
5655
|
hostFactory("pi", () => ctx.plugin(PiLoop, piConfig(config)));
|
|
4457
5656
|
};
|
|
5657
|
+
const mountKimi = () => {
|
|
5658
|
+
const commands = ctx.get("commands");
|
|
5659
|
+
if (commands !== void 0) {
|
|
5660
|
+
const disposers = [];
|
|
5661
|
+
for (const command of KIMI_COMMANDS) {
|
|
5662
|
+
try {
|
|
5663
|
+
disposers.push(commands.register(command));
|
|
5664
|
+
} catch (error) {
|
|
5665
|
+
ctx.logger.warn(`loop-engine: skip kimi command /${command.name}: ${String(error)}`);
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
commandDisposers = disposers;
|
|
5669
|
+
}
|
|
5670
|
+
const skills = ctx.get("skills");
|
|
5671
|
+
if (skills !== void 0) {
|
|
5672
|
+
skillDisposer = skills.registerProvider((control) => new KimiSkillProvider(control));
|
|
5673
|
+
}
|
|
5674
|
+
hostFactory("kimi", () => ctx.plugin(KimiLoop, kimiConfig(config)));
|
|
5675
|
+
};
|
|
4458
5676
|
const mountEngine = (engine) => {
|
|
4459
5677
|
if (engine === "claude-code") mountClaude();
|
|
4460
5678
|
else if (engine === "codex") mountCodex();
|
|
4461
5679
|
else if (engine === "pi") mountPi();
|
|
5680
|
+
else if (engine === "kimi") mountKimi();
|
|
4462
5681
|
};
|
|
4463
5682
|
const unmountEngine = () => {
|
|
4464
5683
|
const fiber = engineFiber;
|
|
@@ -4497,7 +5716,7 @@ function apply(ctx, config) {
|
|
|
4497
5716
|
});
|
|
4498
5717
|
}
|
|
4499
5718
|
export {
|
|
4500
|
-
|
|
5719
|
+
Config5 as Config,
|
|
4501
5720
|
apply,
|
|
4502
5721
|
inject,
|
|
4503
5722
|
name,
|