dsh-loop-engine 1.0.0-rc6 → 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 +1540 -325
- package/lib/invariant.js +2 -2
- 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,230 +3640,1094 @@ 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 === "") {
|
|
3716
|
-
result = text;
|
|
3717
|
-
} else {
|
|
3718
|
-
const base = ensureTrailingNewline(text);
|
|
3719
|
-
result = `${base}
|
|
3720
|
-
${block}`;
|
|
3721
|
-
}
|
|
3722
|
-
} else if (block === "") {
|
|
3723
|
-
result = span.tail.startsWith("\n") ? `${span.head}${span.tail.slice(1)}` : `${span.head}${span.tail}`;
|
|
3724
|
-
} else {
|
|
3725
|
-
result = `${span.head}${span.blankBefore ? "\n" : ""}${block}${span.tail}`;
|
|
3726
|
-
}
|
|
3727
|
-
if (block !== "") return dropSeedPlaceholder(result);
|
|
3728
|
-
if (text.trim() === "") return result;
|
|
3729
|
-
return hasRootEntry(result) ? result : seedEmptyArray(result);
|
|
3704
|
+
function isPermissionRequestFrame(frame2) {
|
|
3705
|
+
return frame2.method === "session/request_permission" && typeof frame2.id === "number";
|
|
3730
3706
|
}
|
|
3731
3707
|
|
|
3732
|
-
// src/
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
return (
|
|
3740
|
-
invocation.agent.followup(createUserMessage4({
|
|
3741
|
-
content: [{ type: "text", text: `/${name2}${invocation.rawInput}` }],
|
|
3742
|
-
source: { kind: "user" }
|
|
3743
|
-
}));
|
|
3744
|
-
return { kind: "success" };
|
|
3745
|
-
};
|
|
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);
|
|
3746
3716
|
}
|
|
3747
|
-
function
|
|
3748
|
-
|
|
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
|
+
};
|
|
3749
3728
|
}
|
|
3750
|
-
var
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
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
|
+
});
|
|
3765
3746
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
const description = commandDescription(raw);
|
|
3780
|
-
if (description === void 0) continue;
|
|
3781
|
-
seen.add(name2);
|
|
3782
|
-
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;
|
|
3783
3760
|
}
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
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");
|
|
3801
3806
|
}
|
|
3802
|
-
|
|
3807
|
+
return sessionId;
|
|
3803
3808
|
}
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
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 }] });
|
|
3808
3812
|
}
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
if (firstLine !== "---") return void 0;
|
|
3825
|
-
const start = firstLineEnd + 1;
|
|
3826
|
-
const closing = findClosingFrontmatter(raw, start);
|
|
3827
|
-
if (closing === void 0) return void 0;
|
|
3828
|
-
const yaml = raw.slice(start, closing.start);
|
|
3829
|
-
const data = {};
|
|
3830
|
-
const lines = yaml.split("\n");
|
|
3831
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
3832
|
-
const line = lines[index] ?? "";
|
|
3833
|
-
const trimmed = line.trim();
|
|
3834
|
-
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
3835
|
-
const colon = trimmed.indexOf(":");
|
|
3836
|
-
if (colon < 0) continue;
|
|
3837
|
-
const key = trimmed.slice(0, colon).trim();
|
|
3838
|
-
const value = trimmed.slice(colon + 1).trim();
|
|
3839
|
-
if (key.length === 0) continue;
|
|
3840
|
-
if (value === ">" || value === ">-" || value === "|" || value === "|-") {
|
|
3841
|
-
const block = [];
|
|
3842
|
-
while (index + 1 < lines.length) {
|
|
3843
|
-
const next = lines[index + 1] ?? "";
|
|
3844
|
-
if (next.trim().length > 0 && !/^[\s]/.test(next)) break;
|
|
3845
|
-
index += 1;
|
|
3846
|
-
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;
|
|
3847
3828
|
}
|
|
3848
|
-
|
|
3849
|
-
|
|
3829
|
+
if (this.sealed) return;
|
|
3830
|
+
await new Promise((resolve5) => {
|
|
3831
|
+
this.updateWake = resolve5;
|
|
3832
|
+
});
|
|
3833
|
+
this.updateWake = void 0;
|
|
3850
3834
|
}
|
|
3851
|
-
data[key] = unquote(value);
|
|
3852
3835
|
}
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
const
|
|
3859
|
-
|
|
3860
|
-
|
|
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?.();
|
|
3845
|
+
}
|
|
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);
|
|
3861
3857
|
}
|
|
3862
3858
|
}
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
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;
|
|
3867
4731
|
while (lineStart <= raw.length) {
|
|
3868
4732
|
const nextNewline = raw.indexOf("\n", lineStart);
|
|
3869
4733
|
const lineEnd = nextNewline < 0 ? raw.length : nextNewline;
|
|
@@ -4008,94 +4872,417 @@ async function tryParseSkill(path) {
|
|
|
4008
4872
|
} catch {
|
|
4009
4873
|
return void 0;
|
|
4010
4874
|
}
|
|
4011
|
-
}
|
|
4012
|
-
async function findProjectRoot(cwd) {
|
|
4013
|
-
let current = cwd;
|
|
4014
|
-
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) {
|
|
4015
5077
|
try {
|
|
4016
|
-
await
|
|
4017
|
-
return
|
|
5078
|
+
const raw = await readFile3(path, { encoding: "utf8" });
|
|
5079
|
+
return parseSkillFile(raw);
|
|
4018
5080
|
} catch {
|
|
5081
|
+
return void 0;
|
|
4019
5082
|
}
|
|
4020
|
-
const parent = resolve(current, "..");
|
|
4021
|
-
if (parent === current) return cwd;
|
|
4022
|
-
current = parent;
|
|
4023
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
|
+
};
|
|
4024
5096
|
}
|
|
5097
|
+
function builtin(name2, description) {
|
|
5098
|
+
return { name: name2, description, handler: forwardKimiCommand(name2) };
|
|
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
|
+
];
|
|
4025
5112
|
|
|
4026
|
-
// src/
|
|
4027
|
-
import
|
|
4028
|
-
import {
|
|
5113
|
+
// src/settings.ts
|
|
5114
|
+
import z5 from "@deepseek-ai/schemastery";
|
|
5115
|
+
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
4029
5116
|
|
|
4030
|
-
// src/
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
}
|
|
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);
|
|
4042
5128
|
}
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
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");
|
|
4050
5143
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
}
|
|
4056
|
-
for (const name2 of policy.primary) {
|
|
4057
|
-
const candidate = join5(dir, name2);
|
|
4058
|
-
if (await pathExists(candidate)) return candidate;
|
|
4059
|
-
}
|
|
4060
|
-
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";
|
|
4061
5148
|
}
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
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}`;
|
|
4068
5196
|
}
|
|
5197
|
+
if (block !== "") return dropSeedPlaceholder(result);
|
|
5198
|
+
if (text.trim() === "") return result;
|
|
5199
|
+
return hasRootEntry(result) ? result : seedEmptyArray(result);
|
|
4069
5200
|
}
|
|
4070
|
-
|
|
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;
|
|
4071
5231
|
try {
|
|
4072
|
-
|
|
5232
|
+
entries = readdirSync(userCommandsDir(), { encoding: "utf8" });
|
|
4073
5233
|
} catch {
|
|
4074
|
-
return
|
|
5234
|
+
return [];
|
|
4075
5235
|
}
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
for (const
|
|
4079
|
-
|
|
4080
|
-
|
|
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) });
|
|
4081
5253
|
}
|
|
4082
|
-
return
|
|
5254
|
+
return definitions;
|
|
4083
5255
|
}
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
return raw !== void 0 && raw.trim().length > 0;
|
|
5256
|
+
function userCommandsDir() {
|
|
5257
|
+
return join7(homedir4(), ".claude", "commands");
|
|
4087
5258
|
}
|
|
4088
|
-
|
|
4089
|
-
const
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
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);
|
|
4093
5273
|
}
|
|
4094
|
-
|
|
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;
|
|
4095
5280
|
}
|
|
4096
5281
|
|
|
4097
5282
|
// src/engine-codex/skills.ts
|
|
4098
|
-
|
|
5283
|
+
import { homedir as homedir5 } from "node:os";
|
|
5284
|
+
import { join as join8 } from "node:path";
|
|
5285
|
+
var PROVIDER_NAME3 = "codex";
|
|
4099
5286
|
var CODEX_PROJECT_RANK = 140;
|
|
4100
5287
|
var CODEX_USER_RANK = 160;
|
|
4101
5288
|
var CODEX_CONTEXT_POLICY = { primary: ["AGENTS.md"] };
|
|
@@ -4104,7 +5291,7 @@ var CodexSkillProvider = class {
|
|
|
4104
5291
|
this.control = control;
|
|
4105
5292
|
}
|
|
4106
5293
|
control;
|
|
4107
|
-
name =
|
|
5294
|
+
name = PROVIDER_NAME3;
|
|
4108
5295
|
async list(options) {
|
|
4109
5296
|
const candidates = [];
|
|
4110
5297
|
const cwd = options.cwd;
|
|
@@ -4112,7 +5299,7 @@ var CodexSkillProvider = class {
|
|
|
4112
5299
|
const paths = await collectProjectContextFiles(cwd, CODEX_CONTEXT_POLICY);
|
|
4113
5300
|
if (await anySourceNonEmpty(paths)) candidates.push(this.agentsCandidate(paths, CODEX_PROJECT_RANK));
|
|
4114
5301
|
}
|
|
4115
|
-
const userPath =
|
|
5302
|
+
const userPath = join8(homedir5(), ".codex", "AGENTS.md");
|
|
4116
5303
|
if (await fileNonEmpty(userPath)) candidates.push(this.agentsCandidate([userPath], CODEX_USER_RANK));
|
|
4117
5304
|
if (this.control.signal.aborted) return [];
|
|
4118
5305
|
return candidates;
|
|
@@ -4151,10 +5338,10 @@ var CodexSkillProvider = class {
|
|
|
4151
5338
|
};
|
|
4152
5339
|
|
|
4153
5340
|
// src/engine-pi/skills.ts
|
|
4154
|
-
import { readdir as
|
|
4155
|
-
import { homedir as
|
|
4156
|
-
import { dirname as
|
|
4157
|
-
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";
|
|
4158
5345
|
var PI_AGENTS_PROJECT_RANK = 140;
|
|
4159
5346
|
var PI_SKILL_PROJECT_RANK = 150;
|
|
4160
5347
|
var PI_AGENTS_USER_RANK = 160;
|
|
@@ -4165,15 +5352,15 @@ var PI_CONTEXT_POLICY = {
|
|
|
4165
5352
|
};
|
|
4166
5353
|
function piAgentDir() {
|
|
4167
5354
|
const override = process.env.PI_CODING_AGENT_DIR;
|
|
4168
|
-
if (override !== void 0 && override.length > 0) return
|
|
4169
|
-
return
|
|
5355
|
+
if (override !== void 0 && override.length > 0) return resolve4(override);
|
|
5356
|
+
return join9(homedir6(), ".pi", "agent");
|
|
4170
5357
|
}
|
|
4171
5358
|
var PiSkillProvider = class {
|
|
4172
5359
|
constructor(control) {
|
|
4173
5360
|
this.control = control;
|
|
4174
5361
|
}
|
|
4175
5362
|
control;
|
|
4176
|
-
name =
|
|
5363
|
+
name = PROVIDER_NAME4;
|
|
4177
5364
|
async list(options) {
|
|
4178
5365
|
const candidates = [];
|
|
4179
5366
|
const cwd = options.cwd;
|
|
@@ -4182,13 +5369,13 @@ var PiSkillProvider = class {
|
|
|
4182
5369
|
const contextPaths = await collectProjectContextFiles(cwd, PI_CONTEXT_POLICY);
|
|
4183
5370
|
if (await anySourceNonEmpty(contextPaths)) candidates.push(this.agentsCandidate(contextPaths, PI_AGENTS_PROJECT_RANK));
|
|
4184
5371
|
for (const dir of projectDirs) {
|
|
4185
|
-
await this.collectSkillsDir(
|
|
5372
|
+
await this.collectSkillsDir(join9(dir, ".pi", "skills"), PI_SKILL_PROJECT_RANK, candidates);
|
|
4186
5373
|
}
|
|
4187
5374
|
}
|
|
4188
5375
|
const userAgentDir = piAgentDir();
|
|
4189
|
-
const userContext =
|
|
5376
|
+
const userContext = join9(userAgentDir, "AGENTS.md");
|
|
4190
5377
|
if (await fileNonEmpty(userContext)) candidates.push(this.agentsCandidate([userContext], PI_AGENTS_USER_RANK));
|
|
4191
|
-
await this.collectSkillsDir(
|
|
5378
|
+
await this.collectSkillsDir(join9(userAgentDir, "skills"), PI_SKILL_USER_RANK, candidates);
|
|
4192
5379
|
if (this.control.signal.aborted) return [];
|
|
4193
5380
|
return candidates;
|
|
4194
5381
|
}
|
|
@@ -4206,7 +5393,7 @@ var PiSkillProvider = class {
|
|
|
4206
5393
|
provider: this.name,
|
|
4207
5394
|
content: parsed.content,
|
|
4208
5395
|
path: locator.path,
|
|
4209
|
-
resourceBase: { kind: "directory", path:
|
|
5396
|
+
resourceBase: { kind: "directory", path: dirname4(locator.path) }
|
|
4210
5397
|
};
|
|
4211
5398
|
}
|
|
4212
5399
|
const content = await readSources(locator.paths);
|
|
@@ -4242,16 +5429,16 @@ var PiSkillProvider = class {
|
|
|
4242
5429
|
async collectSkillsDir(skillsDir, rank, candidates) {
|
|
4243
5430
|
let entries;
|
|
4244
5431
|
try {
|
|
4245
|
-
entries = await
|
|
5432
|
+
entries = await readdir3(skillsDir, { withFileTypes: true, encoding: "utf8" });
|
|
4246
5433
|
} catch {
|
|
4247
5434
|
return;
|
|
4248
5435
|
}
|
|
4249
5436
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4250
|
-
const entryPath =
|
|
4251
|
-
const info = await
|
|
5437
|
+
const entryPath = join9(skillsDir, entry.name);
|
|
5438
|
+
const info = await stat4(entryPath).catch(() => void 0);
|
|
4252
5439
|
if (info === void 0) continue;
|
|
4253
5440
|
if (info.isDirectory()) {
|
|
4254
|
-
const path =
|
|
5441
|
+
const path = join9(entryPath, "SKILL.md");
|
|
4255
5442
|
const parsed2 = await this.tryParse(path);
|
|
4256
5443
|
if (parsed2 === void 0) continue;
|
|
4257
5444
|
candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
|
|
@@ -4281,7 +5468,7 @@ var PiSkillProvider = class {
|
|
|
4281
5468
|
/** Parse one SKILL.md file, or `undefined` when it is unreadable or invalid. */
|
|
4282
5469
|
async tryParse(path) {
|
|
4283
5470
|
try {
|
|
4284
|
-
const raw = await
|
|
5471
|
+
const raw = await readFile4(path, { encoding: "utf8" });
|
|
4285
5472
|
return parseSkillFile(raw);
|
|
4286
5473
|
} catch {
|
|
4287
5474
|
return void 0;
|
|
@@ -4294,23 +5481,24 @@ var name = "loop-engine";
|
|
|
4294
5481
|
var inject = [];
|
|
4295
5482
|
var MAX_MOUNT_ATTEMPTS = 40;
|
|
4296
5483
|
var MOUNT_RETRY_MS = 50;
|
|
4297
|
-
var
|
|
4298
|
-
profile:
|
|
4299
|
-
patchFilename:
|
|
4300
|
-
patchPath:
|
|
4301
|
-
permissionMode:
|
|
4302
|
-
env:
|
|
4303
|
-
model:
|
|
4304
|
-
disposeGraceMs:
|
|
4305
|
-
maxTurns:
|
|
4306
|
-
sandboxMode:
|
|
4307
|
-
approvalPolicy:
|
|
4308
|
-
piProvider:
|
|
4309
|
-
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()
|
|
4310
5498
|
});
|
|
4311
5499
|
function resolvePatchPath(config) {
|
|
4312
5500
|
if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
|
|
4313
|
-
return
|
|
5501
|
+
return join10(
|
|
4314
5502
|
resolveDshHome(),
|
|
4315
5503
|
"profiles",
|
|
4316
5504
|
config.profile ?? "web",
|
|
@@ -4322,20 +5510,20 @@ function isMissing(error) {
|
|
|
4322
5510
|
}
|
|
4323
5511
|
async function readPatchOrUndefined(path) {
|
|
4324
5512
|
try {
|
|
4325
|
-
return await
|
|
5513
|
+
return await readFile5(path, "utf8");
|
|
4326
5514
|
} catch (error) {
|
|
4327
5515
|
if (isMissing(error)) return void 0;
|
|
4328
5516
|
throw error;
|
|
4329
5517
|
}
|
|
4330
5518
|
}
|
|
4331
5519
|
async function writePatchFile(path, text) {
|
|
4332
|
-
await mkdir(
|
|
5520
|
+
await mkdir(dirname5(path), { recursive: true });
|
|
4333
5521
|
const tmp = `${path}.tmp-${randomUUID()}`;
|
|
4334
5522
|
await writeFile(tmp, text, "utf8");
|
|
4335
5523
|
await rename(tmp, path);
|
|
4336
5524
|
}
|
|
4337
5525
|
function writePatchFileSync(path, text) {
|
|
4338
|
-
mkdirSync(
|
|
5526
|
+
mkdirSync(dirname5(path), { recursive: true });
|
|
4339
5527
|
const tmp = `${path}.tmp-${randomUUID()}`;
|
|
4340
5528
|
writeFileSync(tmp, text, "utf8");
|
|
4341
5529
|
renameSync(tmp, path);
|
|
@@ -4381,6 +5569,13 @@ function piConfig(config) {
|
|
|
4381
5569
|
...config.sandboxMode === void 0 ? {} : { sandboxMode: config.sandboxMode }
|
|
4382
5570
|
};
|
|
4383
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
|
+
}
|
|
4384
5579
|
function apply(ctx, config) {
|
|
4385
5580
|
const patchPath = resolvePatchPath(config);
|
|
4386
5581
|
let fileEngine = currentEngineOf(readPatchFileSync(patchPath));
|
|
@@ -4459,10 +5654,30 @@ function apply(ctx, config) {
|
|
|
4459
5654
|
}
|
|
4460
5655
|
hostFactory("pi", () => ctx.plugin(PiLoop, piConfig(config)));
|
|
4461
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
|
+
};
|
|
4462
5676
|
const mountEngine = (engine) => {
|
|
4463
5677
|
if (engine === "claude-code") mountClaude();
|
|
4464
5678
|
else if (engine === "codex") mountCodex();
|
|
4465
5679
|
else if (engine === "pi") mountPi();
|
|
5680
|
+
else if (engine === "kimi") mountKimi();
|
|
4466
5681
|
};
|
|
4467
5682
|
const unmountEngine = () => {
|
|
4468
5683
|
const fiber = engineFiber;
|
|
@@ -4501,7 +5716,7 @@ function apply(ctx, config) {
|
|
|
4501
5716
|
});
|
|
4502
5717
|
}
|
|
4503
5718
|
export {
|
|
4504
|
-
|
|
5719
|
+
Config5 as Config,
|
|
4505
5720
|
apply,
|
|
4506
5721
|
inject,
|
|
4507
5722
|
name,
|