@ycodium-ai/agent-acp 0.2.2167 → 0.2.2510

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,30 @@ 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
- }
26589
+ // src/mcpServers.ts
26590
+ function toAcpMcpServers(entries) {
26591
+ return (entries ?? []).map(
26592
+ (entry) => entry.transport.type === "stdio" ? {
26593
+ name: entry.name,
26594
+ command: entry.transport.command,
26595
+ args: [...entry.transport.args ?? []],
26596
+ env: Object.entries(entry.transport.env ?? {}).map(([name, value]) => ({ name, value }))
26597
+ } : {
26598
+ type: entry.transport.type,
26599
+ name: entry.name,
26600
+ url: entry.transport.url,
26601
+ headers: Object.entries(entry.transport.headers ?? {}).map(([name, value]) => ({
26602
+ name,
26603
+ value
26604
+ }))
26722
26605
  }
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);
26606
+ );
26750
26607
  }
26751
26608
 
26752
- // src/kimiModels.ts
26753
- import {
26754
- ExecutorSpawnRefusedError as ExecutorSpawnRefusedError2
26755
- } from "@ycodium-ai/plugin-api/executor";
26756
-
26757
26609
  // src/probeModels.ts
26758
- import * as NodeFS2 from "node:fs";
26610
+ import * as NodeFS from "node:fs";
26759
26611
  import * as NodeOS from "node:os";
26760
- import * as NodePath2 from "node:path";
26612
+ import * as NodePath from "node:path";
26761
26613
  import {
26762
26614
  ExecutorSpawnRefusedError
26763
26615
  } from "@ycodium-ai/plugin-api/executor";
@@ -26767,18 +26619,18 @@ var CLIENT_INFO = { name: "ycodium-agent-acp-probe", version: "0.2.0" };
26767
26619
  var ACP_EFFORT_OPTION_ID = "reasoning";
26768
26620
  function acpProbeSessionCwd(instanceId) {
26769
26621
  const safe = instanceId.replace(/[^a-zA-Z0-9_-]/g, "_");
26770
- return NodePath2.join(NodeOS.tmpdir(), `ycodium-acp-probe-${safe}`);
26622
+ return NodePath.join(NodeOS.tmpdir(), `ycodium-acp-probe-${safe}`);
26771
26623
  }
26772
- function isRecord8(value) {
26624
+ function isRecord7(value) {
26773
26625
  return typeof value === "object" && value !== null && !Array.isArray(value);
26774
26626
  }
26775
26627
  function asString3(value) {
26776
26628
  return typeof value === "string" ? value : void 0;
26777
26629
  }
26778
26630
  function findAcpProbeSessionId(listed, probeCwd) {
26779
- if (!isRecord8(listed) || !Array.isArray(listed["sessions"])) return void 0;
26631
+ if (!isRecord7(listed) || !Array.isArray(listed["sessions"])) return void 0;
26780
26632
  for (const entry of listed["sessions"]) {
26781
- if (!isRecord8(entry)) continue;
26633
+ if (!isRecord7(entry)) continue;
26782
26634
  if (asString3(entry["cwd"]) !== probeCwd) continue;
26783
26635
  const sessionId = asString3(entry["sessionId"]);
26784
26636
  if (sessionId !== void 0) return sessionId;
@@ -26795,7 +26647,7 @@ async function probeSessionSetup(handshake, probeCwd) {
26795
26647
  return handshake.request("session/new", { cwd: probeCwd, mcpServers: [] });
26796
26648
  }
26797
26649
  function acpProbeModels(setup) {
26798
- if (!isRecord8(setup)) return void 0;
26650
+ if (!isRecord7(setup)) return void 0;
26799
26651
  const rawOptions = setup["configOptions"];
26800
26652
  const choices = acpModelChoicesFromConfigOptions(rawOptions) ?? acpModelChoicesFromSessionModels(setup["models"]);
26801
26653
  if (choices === void 0 || choices.length === 0) return void 0;
@@ -26830,7 +26682,7 @@ async function probeAcpModelsViaSession(input2) {
26830
26682
  const { recipe, host, env } = input2;
26831
26683
  const probeCwd = acpProbeSessionCwd(host.instanceId);
26832
26684
  try {
26833
- NodeFS2.mkdirSync(probeCwd, { recursive: true });
26685
+ NodeFS.mkdirSync(probeCwd, { recursive: true });
26834
26686
  } catch (error62) {
26835
26687
  return { kind: "failed", detail: probeFailureDetail(error62), version: null };
26836
26688
  }
@@ -26882,113 +26734,6 @@ async function probeAcpModelsViaSession(input2) {
26882
26734
  }
26883
26735
  }
26884
26736
 
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
26737
  // src/runtimeModeApproval.ts
26993
26738
  function acpRuntimeModeAutoApproves(runtimeMode, toolKind) {
26994
26739
  switch (runtimeMode) {
@@ -27046,7 +26791,7 @@ var EventBus = class {
27046
26791
  function expandHomePath(value) {
27047
26792
  if (value === "~") return NodeOS2.homedir();
27048
26793
  if (value.startsWith("~/") || value.startsWith("~\\")) {
27049
- return NodePath3.join(NodeOS2.homedir(), value.slice(2));
26794
+ return NodePath2.join(NodeOS2.homedir(), value.slice(2));
27050
26795
  }
27051
26796
  return value;
27052
26797
  }
@@ -27057,15 +26802,13 @@ function modelIdFromSelection(selection) {
27057
26802
  const model = selection["model"];
27058
26803
  return typeof model === "string" ? model.trim() || void 0 : void 0;
27059
26804
  }
27060
- function buildSpawnEnv(recipe, configuredHomePath, launchEnv = {}) {
26805
+ function buildSpawnEnv(recipe, configuredHomePath) {
27061
26806
  const homePath = configuredHomePath.trim();
27062
26807
  const homeEnvVar = recipe.homeEnvVar.trim();
27063
26808
  const homeInjection = homePath.length > 0 && homeEnvVar.length > 0 ? { [homeEnvVar]: expandHomePath(homePath) } : {};
27064
26809
  return mergeLaunchEnv({
27065
- brainSlots: brainSlotEnvKeys(ACP_CARD),
27066
26810
  accountHome: homeInjection,
27067
- instanceTable: recipe.environment,
27068
- launch: launchEnv
26811
+ instanceTable: recipe.environment
27069
26812
  });
27070
26813
  }
27071
26814
  function nowIso() {
@@ -27073,31 +26816,9 @@ function nowIso() {
27073
26816
  }
27074
26817
  function acpContinuationKey(recipe) {
27075
26818
  const homePath = recipe.homePath.trim();
27076
- const resolvedHomePath = homePath.length > 0 ? NodePath3.resolve(expandHomePath(homePath)) : "";
26819
+ const resolvedHomePath = homePath.length > 0 ? NodePath2.resolve(expandHomePath(homePath)) : "";
27077
26820
  return `acp:home:${recipe.binaryPath.trim()}\0${resolvedHomePath}`;
27078
26821
  }
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
26822
  async function stopBooked(booked) {
27102
26823
  await booked.executor.dispose().catch(() => void 0);
27103
26824
  await booked.handle.kill("SIGTERM").catch(() => void 0);
@@ -27125,7 +26846,7 @@ var createAcpInstance = (config2, host) => {
27125
26846
  };
27126
26847
  }
27127
26848
  const env = buildSpawnEnv(recipe, recipe.homePath);
27128
- const outcome = isKimiInstance(recipe) ? await probeKimi({ recipe, host, env }) : await probeAcpModelsViaSession({ recipe, host, env });
26849
+ const outcome = await probeAcpModelsViaSession({ recipe, host, env });
27129
26850
  switch (outcome.kind) {
27130
26851
  case "not-started":
27131
26852
  return {
@@ -27165,6 +26886,8 @@ var createAcpInstance = (config2, host) => {
27165
26886
  };
27166
26887
  }
27167
26888
  },
26889
+ // 暂不处理 input.readOnlyDirectories(有意为之):acp 经共享的 ycodium-acp-executor 起会话,
26890
+ // 还没用真机确认这家 CLI 对 cwd 之外的读怎么处理、要不要放行。确认之后再接这一栏。
27168
26891
  startSession: async (input2, signal) => {
27169
26892
  if (disposed) throw new Error("instance disposed");
27170
26893
  if (signal?.aborted) throw new Error("aborted");
@@ -27178,11 +26901,7 @@ var createAcpInstance = (config2, host) => {
27178
26901
  await stopBooked(existing);
27179
26902
  threads.delete(input2.threadId);
27180
26903
  }
27181
- const env = buildSpawnEnv(
27182
- recipe,
27183
- input2.launch.homePath?.trim() || recipe.homePath,
27184
- input2.launch.processEnv
27185
- );
26904
+ const env = buildSpawnEnv(recipe, recipe.homePath);
27186
26905
  const handle = await host.process.spawn({
27187
26906
  program: recipe.binaryPath,
27188
26907
  args: tokenizeLaunchArgs(recipe.launchArgs),
@@ -27192,12 +26911,11 @@ var createAcpInstance = (config2, host) => {
27192
26911
  });
27193
26912
  const { readable, writable } = nodeStreamsFromHandle2(handle);
27194
26913
  const resume = input2.discardResumeCursor === true ? void 0 : parseAcpResumeCursor(input2.resumeCursor, RESUME_SCHEMA_VERSION);
27195
- const requestedModelId = input2.launch.requestedModelId?.trim() || modelIdFromSelection(input2.modelSelection);
26914
+ const requestedModelId = modelIdFromSelection(input2.modelSelection);
27196
26915
  const requestedEffort = modelSelectionStringOption(
27197
26916
  input2.modelSelection,
27198
26917
  ACP_EFFORT_OPTION_ID
27199
26918
  );
27200
- const hooks = vendorHooksFor(recipe, input2.launch.homePath);
27201
26919
  const autoApprove = input2.nativeModeId === void 0 ? (request) => acpRuntimeModeAutoApproves(
27202
26920
  threads.get(input2.threadId)?.session.runtimeMode ?? input2.runtimeMode,
27203
26921
  request.kind
@@ -27214,9 +26932,11 @@ var createAcpInstance = (config2, host) => {
27214
26932
  log: host.log,
27215
26933
  ...requestedModelId === void 0 ? {} : { requestedModelId },
27216
26934
  ...input2.bindingGeneration === void 0 ? {} : { originBindingGeneration: input2.bindingGeneration },
27217
- ...hooks === void 0 ? {} : { hooks },
27218
26935
  ...autoApprove === void 0 ? {} : { autoApprove },
27219
- ...resume === void 0 ? {} : { resumeSessionId: resume.sessionId }
26936
+ ...resume === void 0 ? {} : { resumeSessionId: resume.sessionId },
26937
+ // 内置编排那一条排第一,后面跟用户在插件中心配的;缺了 `mcpServers` 这一栏,
26938
+ // `mcp__ycodium__*` 整套工具在通用 ACP 会话里都不存在。
26939
+ ...input2.mcpServers === void 0 || input2.mcpServers.length === 0 ? {} : { mcpServers: toAcpMcpServers(input2.mcpServers) }
27220
26940
  });
27221
26941
  const forwarding = (async () => {
27222
26942
  for await (const event of executor.events) {
@@ -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.2167",
3
+ "version": "0.2.2510",
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,10 +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.2167",
24
+ "@ycodium-ai/plugin-api": "0.2.2510",
24
25
  "ycodium-executor-testing": "0.0.0",
25
- "ycodium-acp-executor": "0.0.0",
26
- "ycodium-acp-protocol": "0.0.0"
26
+ "ycodium-acp-executor": "0.2.2510"
27
27
  },
28
28
  "ycodium": {
29
29
  "plugin": "./dist/index.js",