dsh-loop-engine 1.0.0-rc5 → 1.0.0-rc7

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