@ycodium-ai/agent-acp 0.2.2143 → 0.2.2488

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 CHANGED
@@ -1,31 +1,18 @@
1
1
  # @ycodium-ai/agent-acp
2
2
 
3
3
  Generic ACP executor plugin. The user supplies the CLI (`binaryPath` + `launchArgs`); this package
4
- spawns it over stdio and runs `ycodium-acp-executor` against `ycodium-acp-protocol`.
4
+ spawns it over stdio and runs the shared `ycodium-acp-executor` state machine.
5
5
 
6
6
  - `driverKind` is `acp`, matching the existing generic ACP envelope (`driver: "acp"` plus
7
7
  `config.binaryPath` / `config.launchArgs`). Disk format is unchanged.
8
8
  - `createInstance` reads that envelope (or a bare settings object) and `startSession` spawns via
9
9
  lease-scoped `host.process.spawn`.
10
10
  - Manifest permission is `{ kind: "executor" }`.
11
- - Vendor differences go through `AcpVendorHooks`, never through a second state machine. Today that
12
- is Kimi Code (`src/kimi.ts`): its `AskUserQuestion` / `ExitPlanMode` ride on
13
- `session/request_permission` and must stay structured user input, and its context usage is read
14
- from the CLI's own session log. The instance is recognised by `config.providerId === "kimiCode"`
15
- or `homeEnvVar === "KIMI_CODE_HOME"` — both are prefilled by the provider table and hidden from
16
- the form, so they beat the user-editable program path.
17
-
18
- One card, six grid cells: the five ACP vendor rows in the provider table
19
- (`kimiCode` / `githubCopilot` / `geminiCli` / `qwenCode` / `hermesAgent`) plus the user-filled
20
- "Other agent (ACP)" all create `driver: "acp"` instances and are served by this package.
11
+ - There are no vendor presets. A CLI that needs its own behaviour (sign-in, model listing, session
12
+ logs) gets its own executor plugin built on the same shared ACP machinery, as `agent-kimi` does.
21
13
 
22
14
  Shipped pre-installed as `core.agent-acp`; the host's built-in `acp` driver was deleted on
23
- 2026-09-18 (contract table `executorPlugin`).
24
-
25
- Golden replay (`src/replay.test.ts`) runs the real bytes recorded from `kimi acp` 2.0.1 through
26
- `test/wireReplayPeer.mjs`. Re-record with
27
- `apps/server/scripts/record-acp-session.ts`; provenance and what is **not** capturable on this
28
- machine are in `docs/internals/provider-recordings.md`.
15
+ 2026-09-18.
29
16
 
30
17
  Develop with `vp run --filter @ycodium-ai/agent-acp typecheck`, `build`, and `test`.
31
18
 
package/dist/index.js CHANGED
@@ -9,12 +9,8 @@ import { definePlugin } from "@ycodium-ai/plugin-api";
9
9
 
10
10
  // src/acpInstance.ts
11
11
  import * as NodeOS2 from "node:os";
12
- import * as NodePath3 from "node:path";
13
- import {
14
- mergeLaunchEnv,
15
- modelSelectionStringOption,
16
- brainSlotEnvKeys
17
- } from "@ycodium-ai/plugin-api/executor";
12
+ import * as NodePath2 from "node:path";
13
+ import { mergeLaunchEnv, modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
18
14
  import { nodeStreamsFromHandle as nodeStreamsFromHandle2 } from "@ycodium-ai/plugin-api/process-stdin";
19
15
 
20
16
  // ../../packages/ycodium-acp-executor/src/executor.ts
@@ -23405,10 +23401,44 @@ var legacyClientNotificationMethods = /* @__PURE__ */ new Set([
23405
23401
  CLIENT_METHODS.elicitation_complete
23406
23402
  ]);
23407
23403
 
23408
- // ../../packages/ycodium-acp-protocol/src/connection.ts
23404
+ // ../../packages/ycodium-acp-executor/src/connection.ts
23409
23405
  import * as NodeStream from "node:stream";
23410
23406
 
23411
- // ../../packages/ycodium-acp-protocol/src/errors.ts
23407
+ // ../../packages/ycodium-acp-executor/src/errors.ts
23408
+ var AcpExecutorError = class extends Error {
23409
+ code;
23410
+ constructor(code, message) {
23411
+ super(message);
23412
+ this.name = "AcpExecutorError";
23413
+ this.code = code;
23414
+ }
23415
+ };
23416
+ var AcpExecutorDisposedError = class extends AcpExecutorError {
23417
+ constructor(message = "ACP executor has been disposed") {
23418
+ super("disposed", message);
23419
+ this.name = "AcpExecutorDisposedError";
23420
+ }
23421
+ };
23422
+ var AcpExecutorNotStartedError = class extends AcpExecutorError {
23423
+ constructor(message = "ACP session has not been started") {
23424
+ super("not-started", message);
23425
+ this.name = "AcpExecutorNotStartedError";
23426
+ }
23427
+ };
23428
+ var AcpExecutorCancelTimeoutError = class extends AcpExecutorError {
23429
+ constructor(message = "Timed out waiting for session/prompt to finish after cancellation") {
23430
+ super("cancel-timeout", message);
23431
+ this.name = "AcpExecutorCancelTimeoutError";
23432
+ }
23433
+ };
23434
+ var AcpExecutorRequestError = class extends AcpExecutorError {
23435
+ method;
23436
+ constructor(method, message) {
23437
+ super("request-failed", message);
23438
+ this.name = "AcpExecutorRequestError";
23439
+ this.method = method;
23440
+ }
23441
+ };
23412
23442
  var AcpConnectionClosedError = class extends Error {
23413
23443
  constructor(message = "ACP connection closed") {
23414
23444
  super(message);
@@ -23416,7 +23446,7 @@ var AcpConnectionClosedError = class extends Error {
23416
23446
  }
23417
23447
  };
23418
23448
 
23419
- // ../../packages/ycodium-acp-protocol/src/jsonrpc.ts
23449
+ // ../../packages/ycodium-acp-executor/src/jsonrpc.ts
23420
23450
  function isRecord2(value) {
23421
23451
  return typeof value === "object" && value !== null;
23422
23452
  }
@@ -23454,7 +23484,7 @@ function readJsonRpcError(value) {
23454
23484
  };
23455
23485
  }
23456
23486
 
23457
- // ../../packages/ycodium-acp-protocol/src/line-buffer.ts
23487
+ // ../../packages/ycodium-acp-executor/src/line-buffer.ts
23458
23488
  var newline2 = 10;
23459
23489
  var LineBuffer2 = class {
23460
23490
  #pending = [];
@@ -23498,7 +23528,7 @@ var LineBuffer2 = class {
23498
23528
  }
23499
23529
  };
23500
23530
 
23501
- // ../../packages/ycodium-acp-protocol/src/session-update.ts
23531
+ // ../../packages/ycodium-acp-executor/src/session-update.ts
23502
23532
  var KNOWN_SESSION_UPDATES = /* @__PURE__ */ new Set([
23503
23533
  "user_message_chunk",
23504
23534
  "agent_message_chunk",
@@ -23543,7 +23573,7 @@ function decodeSessionUpdate(payload) {
23543
23573
  return { status: "raw", params: payload };
23544
23574
  }
23545
23575
 
23546
- // ../../packages/ycodium-acp-protocol/src/connection.ts
23576
+ // ../../packages/ycodium-acp-executor/src/connection.ts
23547
23577
  var textDecoder = new TextDecoder();
23548
23578
  var textEncoder = new TextEncoder();
23549
23579
  function asBytes(chunk) {
@@ -24068,42 +24098,6 @@ var UnboundedEventStream = class {
24068
24098
  }
24069
24099
  };
24070
24100
 
24071
- // ../../packages/ycodium-acp-executor/src/errors.ts
24072
- var AcpExecutorError = class extends Error {
24073
- code;
24074
- constructor(code, message) {
24075
- super(message);
24076
- this.name = "AcpExecutorError";
24077
- this.code = code;
24078
- }
24079
- };
24080
- var AcpExecutorDisposedError = class extends AcpExecutorError {
24081
- constructor(message = "ACP executor has been disposed") {
24082
- super("disposed", message);
24083
- this.name = "AcpExecutorDisposedError";
24084
- }
24085
- };
24086
- var AcpExecutorNotStartedError = class extends AcpExecutorError {
24087
- constructor(message = "ACP session has not been started") {
24088
- super("not-started", message);
24089
- this.name = "AcpExecutorNotStartedError";
24090
- }
24091
- };
24092
- var AcpExecutorCancelTimeoutError = class extends AcpExecutorError {
24093
- constructor(message = "Timed out waiting for session/prompt to finish after cancellation") {
24094
- super("cancel-timeout", message);
24095
- this.name = "AcpExecutorCancelTimeoutError";
24096
- }
24097
- };
24098
- var AcpExecutorRequestError = class extends AcpExecutorError {
24099
- method;
24100
- constructor(method, message) {
24101
- super("request-failed", message);
24102
- this.name = "AcpExecutorRequestError";
24103
- this.method = method;
24104
- }
24105
- };
24106
-
24107
24101
  // ../../packages/ycodium-acp-executor/src/events.ts
24108
24102
  function canonicalRequestTypeFromAcpKind(kind) {
24109
24103
  switch (kind) {
@@ -26399,57 +26393,7 @@ function acpAgentVersion(initializeResult) {
26399
26393
  const agentInfo = initializeResult["agentInfo"];
26400
26394
  return isRecord5(agentInfo) ? asTrimmedString(agentInfo["version"]) ?? null : null;
26401
26395
  }
26402
- var CLI_VERSION_PATTERN = /(?<![\d.])(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)/;
26403
- function parseCliVersion(output2) {
26404
- return CLI_VERSION_PATTERN.exec(output2)?.[1] ?? null;
26405
- }
26406
- async function collectCappedText(stream, maxBytes) {
26407
- const decoder = new TextDecoder();
26408
- let text = "";
26409
- let total = 0;
26410
- for await (const chunk of stream) {
26411
- total += chunk.byteLength;
26412
- if (total <= maxBytes) text += decoder.decode(chunk, { stream: true });
26413
- }
26414
- return { text: text + decoder.decode(), overflowed: total > maxBytes };
26415
- }
26416
- async function collectProbeProcess(child, input2) {
26417
- try {
26418
- await child.closeStdin().catch(() => void 0);
26419
- const [stdout, stderr, exitCode] = await withProbeTimeout(
26420
- Promise.all([
26421
- collectCappedText(child.stdout, input2.maxOutputBytes),
26422
- collectCappedText(child.stderr, input2.maxOutputBytes),
26423
- child.exited
26424
- ]),
26425
- input2.timeoutMs,
26426
- input2.what
26427
- );
26428
- return { stdout: stdout.overflowed ? void 0 : stdout.text, stderr: stderr.text, exitCode };
26429
- } finally {
26430
- await child.kill("SIGTERM").catch(() => void 0);
26431
- }
26432
- }
26433
26396
  var MAX_VERSION_OUTPUT_BYTES = 64 * 1024;
26434
- async function probeCliVersion(input2) {
26435
- let child;
26436
- try {
26437
- child = await input2.spawn();
26438
- } catch {
26439
- return null;
26440
- }
26441
- try {
26442
- const run = await collectProbeProcess(child, {
26443
- timeoutMs: input2.timeoutMs,
26444
- maxOutputBytes: MAX_VERSION_OUTPUT_BYTES,
26445
- what: "--version"
26446
- });
26447
- if (run.exitCode !== 0) return null;
26448
- return parseCliVersion(run.stdout ?? "") ?? parseCliVersion(run.stderr);
26449
- } catch {
26450
- return null;
26451
- }
26452
- }
26453
26397
 
26454
26398
  // src/card.ts
26455
26399
  var ACP_DRIVER_KIND = "acp";
@@ -26473,9 +26417,6 @@ var ACP_CARD = {
26473
26417
  defaultTextGenerationModel: "",
26474
26418
  customModelIdExample: "",
26475
26419
  modelSlugAliases: null,
26476
- modelsDevProviderIds: [],
26477
- modelsDevSlugPrefixed: false,
26478
- modelSlugPrefix: "",
26479
26420
  mcpTransports: ["stdio", "http", "sse"],
26480
26421
  pluginManaged: true,
26481
26422
  verifiedSkills: false,
@@ -26500,15 +26441,38 @@ var ACP_CARD = {
26500
26441
  },
26501
26442
  credentialSources: [],
26502
26443
  diagnostics: [],
26444
+ // 设置表单照这份画:带 x-ycodium-form 的属性才进表单(enabled、customModels 等另有入口,不在此列),
26445
+ // 顺序看 x-ycodium-form-order;文案键在本插件 locales/<locale>.json 里解析,宿主不兜底。
26503
26446
  configSchema: {
26504
26447
  type: "object",
26448
+ "x-ycodium-form-order": ["binaryPath", "launchArgs", "homePath"],
26505
26449
  properties: {
26506
- binaryPath: { type: "string" },
26507
- launchArgs: { type: "string" },
26508
- homePath: { type: "string" },
26450
+ binaryPath: {
26451
+ type: "string",
26452
+ "x-ycodium-form": {
26453
+ titleKey: "form.binaryPath.title",
26454
+ descriptionKey: "form.binaryPath.description",
26455
+ placeholderKey: "form.binaryPath.placeholder",
26456
+ control: "executable-path"
26457
+ }
26458
+ },
26459
+ launchArgs: {
26460
+ type: "string",
26461
+ "x-ycodium-form": {
26462
+ titleKey: "form.launchArgs.title",
26463
+ descriptionKey: "form.launchArgs.description",
26464
+ placeholderKey: "form.launchArgs.placeholder"
26465
+ }
26466
+ },
26467
+ homePath: {
26468
+ type: "string",
26469
+ "x-ycodium-form": {
26470
+ titleKey: "form.homePath.title",
26471
+ descriptionKey: "form.homePath.description",
26472
+ placeholderKey: "form.homePath.placeholder"
26473
+ }
26474
+ },
26509
26475
  homeEnvVar: { type: "string" },
26510
- providerId: { type: "string" },
26511
- packageName: { type: "string" },
26512
26476
  enabled: { type: "boolean" },
26513
26477
  customModels: { type: "array" },
26514
26478
  driver: { type: "string" },
@@ -26519,14 +26483,10 @@ var ACP_CARD = {
26519
26483
  },
26520
26484
  modelConnectivity: {
26521
26485
  dialects: ["anthropic-messages", "openai-responses", "openai-chat-completions"],
26522
- // **通用 ACP 这张名片接不了外部端点,而且接不了是对的。** ACP 协议本身没有端点
26523
- // 概念,地址与钥匙全靠各家 CLI 自己的环境变量,五家各不相同(kimi 的
26524
- // `KIMI_MODEL_*`、qwen 的 `OPENAI_*`、gemini 的 `GOOGLE_GEMINI_*`……)。这张名片
26525
- // 是认不出是哪一家时的兜底,它不知道该注什么。
26526
- //
26527
- // 原来这里写着 `true` 却一栏开关都不给——名片对外宣称能接,实际绑定时报一个含糊
26528
- // 的 `driver-unknown`,而提案 0025 的进度表据此把 kimi 记成「通」。哪家要接,就为
26529
- // 那一家单出一张名片(`resolveByLocator` 按启动命令认家),别回来改这一张。
26486
+ // **通用 ACP 这张名片说不出怎么接外部地址,而且说不出是对的。** ACP 协议本身没有
26487
+ // 端点概念,地址与钥匙全靠各家 CLI 自己的环境变量,各家各不相同。用户按那家 CLI
26488
+ // 自己的文档在实例上设变量。哪家要写清楚,就为那一家单出一个插件(像 `agent-kimi`),
26489
+ // 别回来改这一张。
26530
26490
  acceptsExternalEndpoints: false
26531
26491
  }
26532
26492
  };
@@ -26560,8 +26520,7 @@ function parseAcpInstanceConfig(config2) {
26560
26520
  homePath: "",
26561
26521
  homeEnvVar: "",
26562
26522
  environment: {},
26563
- envelopeDriver: void 0,
26564
- providerId: ""
26523
+ envelopeDriver: void 0
26565
26524
  };
26566
26525
  }
26567
26526
  const nested = isRecord6(config2["config"]) ? config2["config"] : void 0;
@@ -26572,8 +26531,7 @@ function parseAcpInstanceConfig(config2) {
26572
26531
  homePath: readTrimmed(settings["homePath"]) || readTrimmed(config2["homePath"]),
26573
26532
  homeEnvVar: readTrimmed(settings["homeEnvVar"]) || readTrimmed(config2["homeEnvVar"]),
26574
26533
  environment: readEnvironment(config2["environment"]),
26575
- envelopeDriver: readTrimmed(config2["driver"]) || void 0,
26576
- providerId: readTrimmed(settings["providerId"]) || readTrimmed(config2["providerId"])
26534
+ envelopeDriver: readTrimmed(config2["driver"]) || void 0
26577
26535
  };
26578
26536
  }
26579
26537
  function tokenizeLaunchArgs(value) {
@@ -26628,136 +26586,10 @@ function tokenizeLaunchArgs(value) {
26628
26586
  return tokens;
26629
26587
  }
26630
26588
 
26631
- // src/kimi.ts
26632
- import * as NodeFS from "node:fs";
26633
- import * as NodePath from "node:path";
26634
- var KIMI_PROVIDER_ID = "kimiCode";
26635
- var KIMI_HOME_ENV_VAR = "KIMI_CODE_HOME";
26636
- var KIMI_USER_INPUT_TITLES = /* @__PURE__ */ new Set(["AskUserQuestion", "ExitPlanMode"]);
26637
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
26638
- function questionTextFromContent(content) {
26639
- if (!Array.isArray(content)) return "";
26640
- const parts = [];
26641
- for (const entry of content) {
26642
- if (!isRecord7(entry) || entry["type"] !== "content") continue;
26643
- const inner = entry["content"];
26644
- if (!isRecord7(inner) || inner["type"] !== "text") continue;
26645
- const text = inner["text"];
26646
- if (typeof text === "string") parts.push(text);
26647
- }
26648
- return parts.join("\n\n").trim();
26649
- }
26650
- function kimiPermissionUserInput(request) {
26651
- const title = request.toolCall.title;
26652
- if (typeof title !== "string" || !KIMI_USER_INPUT_TITLES.has(title)) return void 0;
26653
- const ids = new Set(request.options.map((option) => option.optionId));
26654
- if (request.options.length === 0 || ids.size !== request.options.length) {
26655
- throw new Error("Kimi interaction must provide distinct response options.");
26656
- }
26657
- const question = questionTextFromContent(request.toolCall.content);
26658
- if (question.length === 0) {
26659
- throw new Error("Kimi interaction is missing a question or plan.");
26660
- }
26661
- return {
26662
- id: request.toolCall.toolCallId,
26663
- header: title,
26664
- question,
26665
- kind: title === "ExitPlanMode" ? "plan" : "question",
26666
- allowFreeform: false,
26667
- multiSelect: false,
26668
- options: request.options.map((option) => ({
26669
- id: option.optionId,
26670
- label: option.name,
26671
- description: ""
26672
- }))
26673
- };
26674
- }
26675
- var WIRE_LOG_SEGMENTS = ["agents", "main", "wire.jsonl"];
26676
- var SESSIONS_DIR = "sessions";
26677
- var TAIL_SCAN_BYTES = 256 * 1024;
26678
- var positiveInt = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
26679
- function parseKimiContextWindow(contents) {
26680
- let usedTokens;
26681
- let maxTokens;
26682
- for (const line of contents.split("\n")) {
26683
- const trimmed = line.trim();
26684
- if (trimmed.length === 0) continue;
26685
- let record2;
26686
- try {
26687
- record2 = JSON.parse(trimmed);
26688
- } catch {
26689
- continue;
26690
- }
26691
- if (!isRecord7(record2)) continue;
26692
- const type = record2["type"];
26693
- if (type === "token_counting.turn_recorded" || type === "token_counting.measured") {
26694
- usedTokens = positiveInt(record2["tokens"]) ?? usedTokens;
26695
- continue;
26696
- }
26697
- if (type === "llm.request") {
26698
- maxTokens = positiveInt(record2["maxTokens"]) ?? maxTokens;
26699
- }
26700
- }
26701
- if (usedTokens === void 0) return void 0;
26702
- return maxTokens === void 0 ? { usedTokens } : { usedTokens, maxTokens };
26703
- }
26704
- function readTail(filePath) {
26705
- let handle;
26706
- try {
26707
- const size = NodeFS.statSync(filePath).size;
26708
- if (!Number.isFinite(size) || size <= 0) return void 0;
26709
- const length = size > TAIL_SCAN_BYTES ? TAIL_SCAN_BYTES : size;
26710
- const buffer = Buffer.alloc(length);
26711
- handle = NodeFS.openSync(filePath, "r");
26712
- NodeFS.readSync(handle, buffer, 0, length, size - length);
26713
- return buffer.toString("utf8");
26714
- } catch {
26715
- return void 0;
26716
- } finally {
26717
- if (handle !== void 0) {
26718
- try {
26719
- NodeFS.closeSync(handle);
26720
- } catch {
26721
- }
26722
- }
26723
- }
26724
- }
26725
- function findKimiSessionWireLog(input2) {
26726
- const sessionsRoot = NodePath.join(input2.homePath, SESSIONS_DIR);
26727
- let workspaces;
26728
- try {
26729
- workspaces = NodeFS.readdirSync(sessionsRoot);
26730
- } catch {
26731
- return void 0;
26732
- }
26733
- for (const workspace of workspaces) {
26734
- const candidate = NodePath.join(
26735
- sessionsRoot,
26736
- workspace,
26737
- input2.acpSessionId,
26738
- ...WIRE_LOG_SEGMENTS
26739
- );
26740
- if (NodeFS.existsSync(candidate)) return candidate;
26741
- }
26742
- return void 0;
26743
- }
26744
- function readKimiContextWindow(input2) {
26745
- if (input2.homePath.trim().length === 0) return void 0;
26746
- const wireLog = findKimiSessionWireLog(input2);
26747
- if (wireLog === void 0) return void 0;
26748
- const contents = readTail(wireLog);
26749
- return contents === void 0 ? void 0 : parseKimiContextWindow(contents);
26750
- }
26751
-
26752
- // src/kimiModels.ts
26753
- import {
26754
- ExecutorSpawnRefusedError as ExecutorSpawnRefusedError2
26755
- } from "@ycodium-ai/plugin-api/executor";
26756
-
26757
26589
  // src/probeModels.ts
26758
- import * as NodeFS2 from "node:fs";
26590
+ import * as NodeFS from "node:fs";
26759
26591
  import * as NodeOS from "node:os";
26760
- import * as NodePath2 from "node:path";
26592
+ import * as NodePath from "node:path";
26761
26593
  import {
26762
26594
  ExecutorSpawnRefusedError
26763
26595
  } from "@ycodium-ai/plugin-api/executor";
@@ -26767,18 +26599,18 @@ var CLIENT_INFO = { name: "ycodium-agent-acp-probe", version: "0.2.0" };
26767
26599
  var ACP_EFFORT_OPTION_ID = "reasoning";
26768
26600
  function acpProbeSessionCwd(instanceId) {
26769
26601
  const safe = instanceId.replace(/[^a-zA-Z0-9_-]/g, "_");
26770
- return NodePath2.join(NodeOS.tmpdir(), `ycodium-acp-probe-${safe}`);
26602
+ return NodePath.join(NodeOS.tmpdir(), `ycodium-acp-probe-${safe}`);
26771
26603
  }
26772
- function isRecord8(value) {
26604
+ function isRecord7(value) {
26773
26605
  return typeof value === "object" && value !== null && !Array.isArray(value);
26774
26606
  }
26775
26607
  function asString3(value) {
26776
26608
  return typeof value === "string" ? value : void 0;
26777
26609
  }
26778
26610
  function findAcpProbeSessionId(listed, probeCwd) {
26779
- if (!isRecord8(listed) || !Array.isArray(listed["sessions"])) return void 0;
26611
+ if (!isRecord7(listed) || !Array.isArray(listed["sessions"])) return void 0;
26780
26612
  for (const entry of listed["sessions"]) {
26781
- if (!isRecord8(entry)) continue;
26613
+ if (!isRecord7(entry)) continue;
26782
26614
  if (asString3(entry["cwd"]) !== probeCwd) continue;
26783
26615
  const sessionId = asString3(entry["sessionId"]);
26784
26616
  if (sessionId !== void 0) return sessionId;
@@ -26795,7 +26627,7 @@ async function probeSessionSetup(handshake, probeCwd) {
26795
26627
  return handshake.request("session/new", { cwd: probeCwd, mcpServers: [] });
26796
26628
  }
26797
26629
  function acpProbeModels(setup) {
26798
- if (!isRecord8(setup)) return void 0;
26630
+ if (!isRecord7(setup)) return void 0;
26799
26631
  const rawOptions = setup["configOptions"];
26800
26632
  const choices = acpModelChoicesFromConfigOptions(rawOptions) ?? acpModelChoicesFromSessionModels(setup["models"]);
26801
26633
  if (choices === void 0 || choices.length === 0) return void 0;
@@ -26830,7 +26662,7 @@ async function probeAcpModelsViaSession(input2) {
26830
26662
  const { recipe, host, env } = input2;
26831
26663
  const probeCwd = acpProbeSessionCwd(host.instanceId);
26832
26664
  try {
26833
- NodeFS2.mkdirSync(probeCwd, { recursive: true });
26665
+ NodeFS.mkdirSync(probeCwd, { recursive: true });
26834
26666
  } catch (error62) {
26835
26667
  return { kind: "failed", detail: probeFailureDetail(error62), version: null };
26836
26668
  }
@@ -26882,113 +26714,6 @@ async function probeAcpModelsViaSession(input2) {
26882
26714
  }
26883
26715
  }
26884
26716
 
26885
- // src/kimiModels.ts
26886
- var PROBE_TIMEOUT_MS = 15e3;
26887
- var MAX_OUTPUT_BYTES = 1024 * 1024;
26888
- var KIMI_MODEL_LIST_ARGS = ["provider", "list", "--json"];
26889
- function isRecord9(value) {
26890
- return typeof value === "object" && value !== null && !Array.isArray(value);
26891
- }
26892
- function asString4(value) {
26893
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
26894
- }
26895
- function positiveInt2(value) {
26896
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : void 0;
26897
- }
26898
- function kimiProbeModels(stdout) {
26899
- let parsed;
26900
- try {
26901
- parsed = JSON.parse(stdout);
26902
- } catch {
26903
- return void 0;
26904
- }
26905
- if (!isRecord9(parsed)) return void 0;
26906
- const models = parsed["models"];
26907
- if (!isRecord9(models)) return void 0;
26908
- const out = [];
26909
- for (const [slug, entry] of Object.entries(models)) {
26910
- const trimmed = slug.trim();
26911
- if (trimmed.length === 0 || !isRecord9(entry)) continue;
26912
- const name = asString4(entry["displayName"]);
26913
- const contextWindow = positiveInt2(entry["maxContextSize"]);
26914
- out.push({
26915
- slug: trimmed,
26916
- ...name === void 0 ? {} : { name },
26917
- ...contextWindow === void 0 ? {} : { contextWindow }
26918
- });
26919
- }
26920
- return out;
26921
- }
26922
- async function probeKimiModels(input2) {
26923
- const { recipe, host, env } = input2;
26924
- let handle;
26925
- try {
26926
- handle = await host.process.spawn({
26927
- program: recipe.binaryPath,
26928
- // 实例信封里的启动参数(Kimi 那行预填 `acp`)在这一栏用不上:读配置要换成
26929
- // `provider list --json`,留着 `acp` 就等于让它起 ACP 服务然后什么都不问。
26930
- args: KIMI_MODEL_LIST_ARGS,
26931
- cwd: process.cwd(),
26932
- owner: { bindingId: host.bindingId },
26933
- ...Object.keys(env).length > 0 ? { env } : {}
26934
- });
26935
- } catch (error62) {
26936
- if (error62 instanceof ExecutorSpawnRefusedError2) throw error62;
26937
- return { kind: "not-started", detail: probeFailureDetail(error62) };
26938
- }
26939
- try {
26940
- const run = await collectProbeProcess(handle, {
26941
- timeoutMs: input2.timeoutMs ?? PROBE_TIMEOUT_MS,
26942
- // 这份 JSON 实测 1.7KB;封顶是防 CLI 抽风刷日志把内存吃掉。
26943
- maxOutputBytes: MAX_OUTPUT_BYTES,
26944
- what: "kimi provider list"
26945
- });
26946
- if (run.exitCode !== 0) {
26947
- const stderr = run.stderr.trim();
26948
- return {
26949
- kind: "failed",
26950
- detail: stderr.length > 0 ? stderr : run.exitCode === null ? "kimi provider list was killed by a signal" : `kimi provider list exited with code ${run.exitCode}`
26951
- };
26952
- }
26953
- return {
26954
- kind: "answered",
26955
- models: run.stdout === void 0 ? void 0 : kimiProbeModels(run.stdout)
26956
- };
26957
- } catch (error62) {
26958
- return { kind: "failed", detail: probeFailureDetail(error62) };
26959
- }
26960
- }
26961
- async function probeKimiVersion(input2) {
26962
- const { recipe, host, env } = input2;
26963
- return probeCliVersion({
26964
- spawn: () => host.process.spawn({
26965
- program: recipe.binaryPath,
26966
- args: ["--version"],
26967
- cwd: process.cwd(),
26968
- owner: { bindingId: host.bindingId },
26969
- ...Object.keys(env).length > 0 ? { env } : {}
26970
- }),
26971
- timeoutMs: PROBE_TIMEOUT_MS
26972
- });
26973
- }
26974
- async function probeKimi(input2) {
26975
- const [listed, version2] = await Promise.all([probeKimiModels(input2), probeKimiVersion(input2)]);
26976
- switch (listed.kind) {
26977
- case "not-started":
26978
- return { kind: "not-started", detail: listed.detail };
26979
- case "failed":
26980
- return { kind: "failed", detail: listed.detail, version: version2 };
26981
- case "answered":
26982
- return {
26983
- kind: "answered",
26984
- version: version2,
26985
- models: listed.models,
26986
- nativeModes: [],
26987
- currentNativeModeId: void 0
26988
- };
26989
- }
26990
- }
26991
-
26992
26717
  // src/runtimeModeApproval.ts
26993
26718
  function acpRuntimeModeAutoApproves(runtimeMode, toolKind) {
26994
26719
  switch (runtimeMode) {
@@ -27046,7 +26771,7 @@ var EventBus = class {
27046
26771
  function expandHomePath(value) {
27047
26772
  if (value === "~") return NodeOS2.homedir();
27048
26773
  if (value.startsWith("~/") || value.startsWith("~\\")) {
27049
- return NodePath3.join(NodeOS2.homedir(), value.slice(2));
26774
+ return NodePath2.join(NodeOS2.homedir(), value.slice(2));
27050
26775
  }
27051
26776
  return value;
27052
26777
  }
@@ -27057,15 +26782,13 @@ function modelIdFromSelection(selection) {
27057
26782
  const model = selection["model"];
27058
26783
  return typeof model === "string" ? model.trim() || void 0 : void 0;
27059
26784
  }
27060
- function buildSpawnEnv(recipe, configuredHomePath, launchEnv = {}) {
26785
+ function buildSpawnEnv(recipe, configuredHomePath) {
27061
26786
  const homePath = configuredHomePath.trim();
27062
26787
  const homeEnvVar = recipe.homeEnvVar.trim();
27063
26788
  const homeInjection = homePath.length > 0 && homeEnvVar.length > 0 ? { [homeEnvVar]: expandHomePath(homePath) } : {};
27064
26789
  return mergeLaunchEnv({
27065
- brainSlots: brainSlotEnvKeys(ACP_CARD),
27066
26790
  accountHome: homeInjection,
27067
- instanceTable: recipe.environment,
27068
- launch: launchEnv
26791
+ instanceTable: recipe.environment
27069
26792
  });
27070
26793
  }
27071
26794
  function nowIso() {
@@ -27073,31 +26796,9 @@ function nowIso() {
27073
26796
  }
27074
26797
  function acpContinuationKey(recipe) {
27075
26798
  const homePath = recipe.homePath.trim();
27076
- const resolvedHomePath = homePath.length > 0 ? NodePath3.resolve(expandHomePath(homePath)) : "";
26799
+ const resolvedHomePath = homePath.length > 0 ? NodePath2.resolve(expandHomePath(homePath)) : "";
27077
26800
  return `acp:home:${recipe.binaryPath.trim()}\0${resolvedHomePath}`;
27078
26801
  }
27079
- function resolveHomePath(recipe, launchHomePath) {
27080
- const envVar = recipe.homeEnvVar.trim();
27081
- const fromEnvironment = envVar.length > 0 ? recipe.environment[envVar]?.trim() : void 0;
27082
- if (fromEnvironment !== void 0 && fromEnvironment.length > 0) {
27083
- return expandHomePath(fromEnvironment);
27084
- }
27085
- const configured = (launchHomePath?.trim() || recipe.homePath).trim();
27086
- return configured.length > 0 ? expandHomePath(configured) : "";
27087
- }
27088
- function isKimiInstance(recipe) {
27089
- return recipe.providerId === KIMI_PROVIDER_ID || recipe.homeEnvVar.trim() === KIMI_HOME_ENV_VAR;
27090
- }
27091
- function vendorHooksFor(recipe, launchHomePath) {
27092
- if (!isKimiInstance(recipe)) return void 0;
27093
- const homePath = resolveHomePath(recipe, launchHomePath);
27094
- return {
27095
- permissionUserInput: kimiPermissionUserInput,
27096
- // ACP 的 `usage_update` 我们这边不消费,进度圈只能从各家自己写在磁盘上的
27097
- // 记录里读。目前只有 Kimi 的格式核实过。
27098
- readContextWindow: ({ sessionId }) => readKimiContextWindow({ homePath, acpSessionId: sessionId })
27099
- };
27100
- }
27101
26802
  async function stopBooked(booked) {
27102
26803
  await booked.executor.dispose().catch(() => void 0);
27103
26804
  await booked.handle.kill("SIGTERM").catch(() => void 0);
@@ -27125,7 +26826,7 @@ var createAcpInstance = (config2, host) => {
27125
26826
  };
27126
26827
  }
27127
26828
  const env = buildSpawnEnv(recipe, recipe.homePath);
27128
- const outcome = isKimiInstance(recipe) ? await probeKimi({ recipe, host, env }) : await probeAcpModelsViaSession({ recipe, host, env });
26829
+ const outcome = await probeAcpModelsViaSession({ recipe, host, env });
27129
26830
  switch (outcome.kind) {
27130
26831
  case "not-started":
27131
26832
  return {
@@ -27178,11 +26879,7 @@ var createAcpInstance = (config2, host) => {
27178
26879
  await stopBooked(existing);
27179
26880
  threads.delete(input2.threadId);
27180
26881
  }
27181
- const env = buildSpawnEnv(
27182
- recipe,
27183
- input2.launch.homePath?.trim() || recipe.homePath,
27184
- input2.launch.processEnv
27185
- );
26882
+ const env = buildSpawnEnv(recipe, recipe.homePath);
27186
26883
  const handle = await host.process.spawn({
27187
26884
  program: recipe.binaryPath,
27188
26885
  args: tokenizeLaunchArgs(recipe.launchArgs),
@@ -27192,12 +26889,11 @@ var createAcpInstance = (config2, host) => {
27192
26889
  });
27193
26890
  const { readable, writable } = nodeStreamsFromHandle2(handle);
27194
26891
  const resume = input2.discardResumeCursor === true ? void 0 : parseAcpResumeCursor(input2.resumeCursor, RESUME_SCHEMA_VERSION);
27195
- const requestedModelId = input2.launch.requestedModelId?.trim() || modelIdFromSelection(input2.modelSelection);
26892
+ const requestedModelId = modelIdFromSelection(input2.modelSelection);
27196
26893
  const requestedEffort = modelSelectionStringOption(
27197
26894
  input2.modelSelection,
27198
26895
  ACP_EFFORT_OPTION_ID
27199
26896
  );
27200
- const hooks = vendorHooksFor(recipe, input2.launch.homePath);
27201
26897
  const autoApprove = input2.nativeModeId === void 0 ? (request) => acpRuntimeModeAutoApproves(
27202
26898
  threads.get(input2.threadId)?.session.runtimeMode ?? input2.runtimeMode,
27203
26899
  request.kind
@@ -27214,7 +26910,6 @@ var createAcpInstance = (config2, host) => {
27214
26910
  log: host.log,
27215
26911
  ...requestedModelId === void 0 ? {} : { requestedModelId },
27216
26912
  ...input2.bindingGeneration === void 0 ? {} : { originBindingGeneration: input2.bindingGeneration },
27217
- ...hooks === void 0 ? {} : { hooks },
27218
26913
  ...autoApprove === void 0 ? {} : { autoApprove },
27219
26914
  ...resume === void 0 ? {} : { resumeSessionId: resume.sessionId }
27220
26915
  });
@@ -0,0 +1,19 @@
1
+ {
2
+ "form": {
3
+ "binaryPath": {
4
+ "title": "Program path",
5
+ "description": "The ACP program this provider launches.",
6
+ "placeholder": "e.g. my-agent"
7
+ },
8
+ "launchArgs": {
9
+ "title": "Launch arguments",
10
+ "description": "Arguments that put the CLI into ACP stdio mode.",
11
+ "placeholder": "e.g. acp or --acp"
12
+ },
13
+ "homePath": {
14
+ "title": "Account directory",
15
+ "description": "Where this account's login and history live. Leave empty to use the CLI's default.",
16
+ "placeholder": "e.g. ~/.my-agent-work"
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "form": {
3
+ "binaryPath": {
4
+ "title": "程序路径",
5
+ "description": "此 provider 启动的 ACP 程序。",
6
+ "placeholder": "例如 my-agent"
7
+ },
8
+ "launchArgs": {
9
+ "title": "启动参数",
10
+ "description": "让命令行工具进入 ACP stdio 模式的参数。",
11
+ "placeholder": "例如 acp 或 --acp"
12
+ },
13
+ "homePath": {
14
+ "title": "账户目录",
15
+ "description": "此账户的登录信息与历史记录所在的目录。留空则使用该命令行工具的默认目录。",
16
+ "placeholder": "例如 ~/.my-agent-work"
17
+ }
18
+ }
19
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-acp",
3
- "version": "0.2.2143",
3
+ "version": "0.2.2488",
4
4
  "description": "Generic ACP executor: user-supplied CLI over Agent Client Protocol stdio.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "assets",
13
+ "locales",
13
14
  "README.md"
14
15
  ],
15
16
  "type": "module",
@@ -20,9 +21,9 @@
20
21
  "@types/node": "24.12.4",
21
22
  "esbuild": "0.28.1",
22
23
  "vite-plus": "0.2.2",
23
- "@ycodium-ai/plugin-api": "0.2.2143",
24
- "ycodium-acp-executor": "0.0.0",
25
- "ycodium-acp-protocol": "0.0.0"
24
+ "ycodium-acp-executor": "0.2.2488",
25
+ "@ycodium-ai/plugin-api": "0.2.2488",
26
+ "ycodium-executor-testing": "0.0.0"
26
27
  },
27
28
  "ycodium": {
28
29
  "plugin": "./dist/index.js",