@agentconnect.md/daemon 1.0.0-rc.11 → 1.0.0-rc.13

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/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  import { EventEmitter } from "node:events";
4
4
  import childProcess, { execFile, spawn } from "node:child_process";
5
5
  import * as sp from "node:path";
6
- import path, { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
7
- import fs, { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, stat, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
6
+ import path, { delimiter, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
7
+ import fs, { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, stat, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
8
8
  import process$1 from "node:process";
9
9
  import { stripVTControlCharacters } from "node:util";
10
10
  import { fileURLToPath } from "node:url";
@@ -7483,6 +7483,19 @@ async function fetchRegistry(root, opts = {}) {
7483
7483
  clearTimeout(timer);
7484
7484
  }
7485
7485
  }
7486
+ /**
7487
+ * Display names (registry id -> human-facing `name`) read from the local cache.
7488
+ * Used for capability reporting — the daemon advertises the tool name to the CP
7489
+ * rather than the registry id. Entries with no `name` are omitted (callers fall
7490
+ * back to the id). Returns `{}` when no cached registry doc exists.
7491
+ */
7492
+ function cachedRuntimeNames(root) {
7493
+ const doc = readCachedDoc(root);
7494
+ if (!doc) return {};
7495
+ const out = {};
7496
+ for (const [id, entry] of Object.entries(doc.agents)) if (entry.name) out[id] = entry.name;
7497
+ return out;
7498
+ }
7486
7499
  async function defaultRuntimes(root, opts = {}) {
7487
7500
  const cached = (opts.mode ?? "blocking") === "cache-first" ? readCachedDoc(root) : null;
7488
7501
  let doc;
@@ -16858,7 +16871,7 @@ async function runChat(opts) {
16858
16871
  //#region src/version.ts
16859
16872
  const DAEMON_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
16860
16873
  //#endregion
16861
- //#region ../protocol/dist/envelope.js
16874
+ //#region ../protocol/src/envelope.ts
16862
16875
  /**
16863
16876
  * The protocol envelope — every frame, both directions, is wrapped in this.
16864
16877
  * Mirrors daemon-cp-ws-protocol.md §1.1.
@@ -16885,7 +16898,7 @@ object({
16885
16898
  /** The all-zero UUID used when a frame is malformed past the point of reading `id`. */
16886
16899
  const NIL_UUID = "00000000-0000-0000-0000-000000000000";
16887
16900
  //#endregion
16888
- //#region ../protocol/dist/frames/auth.js
16901
+ //#region ../protocol/src/frames/auth.ts
16889
16902
  /**
16890
16903
  * Auth & identity — protocol §3.1 / §3.2.
16891
16904
  *
@@ -16915,7 +16928,7 @@ const AuthOk = object({
16915
16928
  }).optional()
16916
16929
  });
16917
16930
  //#endregion
16918
- //#region ../protocol/dist/frames/route.js
16931
+ //#region ../protocol/src/frames/route.ts
16919
16932
  /**
16920
16933
  * Routing & orchestration (C→D control) — protocol §5.
16921
16934
  *
@@ -16978,7 +16991,7 @@ const DrainProgress = object({
16978
16991
  });
16979
16992
  const DrainDone = object({ released: array(SessionKey) });
16980
16993
  //#endregion
16981
- //#region ../protocol/dist/frames/cron.js
16994
+ //#region ../protocol/src/frames/cron.ts
16982
16995
  /**
16983
16996
  * Cron sinks to the daemon (D5) — protocol §5.4.
16984
16997
  *
@@ -16994,7 +17007,7 @@ const CronUpsert = object({
16994
17007
  });
16995
17008
  const CronRemove = object({ cronId: string().uuid() });
16996
17009
  //#endregion
16997
- //#region ../protocol/dist/frames/secrets.js
17010
+ //#region ../protocol/src/frames/secrets.ts
16998
17011
  /**
16999
17012
  * Secrets (C5 ↔ D10) — protocol §6.
17000
17013
  *
@@ -17033,7 +17046,7 @@ const ScopeAttestation = object({
17033
17046
  exp: string().datetime()
17034
17047
  });
17035
17048
  //#endregion
17036
- //#region ../protocol/dist/frames/register.js
17049
+ //#region ../protocol/src/frames/register.ts
17037
17050
  /**
17038
17051
  * Capability upload + the reconcile snapshot — protocol §3.3.
17039
17052
  *
@@ -17067,7 +17080,7 @@ const RegisterOk = object({
17067
17080
  })
17068
17081
  });
17069
17082
  //#endregion
17070
- //#region ../protocol/dist/frames/agent.js
17083
+ //#region ../protocol/src/frames/agent.ts
17071
17084
  /**
17072
17085
  * Agent lifecycle + delivery (protocol §4.3, §4.4, §7.4, §8).
17073
17086
  *
@@ -17137,7 +17150,7 @@ const AgentScopeDenied = object({
17137
17150
  capability: string()
17138
17151
  });
17139
17152
  //#endregion
17140
- //#region ../protocol/dist/frame.js
17153
+ //#region ../protocol/src/frame.ts
17141
17154
  /**
17142
17155
  * The single source of truth for the wire: `type` string → payload zod schema.
17143
17156
  *
@@ -17302,7 +17315,7 @@ discriminatedUnion("type", [
17302
17315
  frame("error", FRAME_SCHEMAS["error"])
17303
17316
  ]);
17304
17317
  //#endregion
17305
- //#region ../protocol/dist/codec.js
17318
+ //#region ../protocol/src/codec.ts
17306
17319
  /** Soft cap per frame — 256 KiB (protocol §1). Over this → FRAME_TOO_LARGE. */
17307
17320
  const MAX_FRAME_BYTES = 256 * 1024;
17308
17321
  const textEncoder = new TextEncoder();
@@ -17378,7 +17391,7 @@ function encode(frame) {
17378
17391
  return JSON.stringify(frame);
17379
17392
  }
17380
17393
  //#endregion
17381
- //#region ../protocol/dist/index.js
17394
+ //#region ../protocol/src/index.ts
17382
17395
  /**
17383
17396
  * Narrowing guard factory: `isFrame("auth")(frame)` narrows a decoded
17384
17397
  * `AnyFrame` to the member whose `type` matches.
@@ -79998,6 +80011,104 @@ var Scheduler = class {
79998
80011
  }
79999
80012
  };
80000
80013
  //#endregion
80014
+ //#region src/runtimes/probe.ts
80015
+ /**
80016
+ * Host-availability probing for runtimes.
80017
+ *
80018
+ * `resolveRuntimes()` produces a runtime for every ACP-registry entry that has a
80019
+ * distribution for this platform — but that says nothing about whether the tool
80020
+ * is actually runnable *here*. This module answers that question so the daemon
80021
+ * only advertises genuinely-installed runtimes in `RegisterReq.capabilities`.
80022
+ *
80023
+ * Two layers:
80024
+ * 1. A generic launcher check — is the runtime's `command` resolvable? Most
80025
+ * agents ship as `npx`/`uvx` packages, which fetch on demand, so the launcher
80026
+ * being present is enough for them.
80027
+ * 2. Per-runtime custom probes (`CUSTOM_PROBES`) for the cases where (1) is too
80028
+ * weak — almost every agent runs via `npx`, so "npx exists" is meaningless.
80029
+ * For those we additionally look for the wrapped CLI's own config/state dir,
80030
+ * which appears once the user has installed and run/initialized it.
80031
+ *
80032
+ * The custom probes test for the config *directory* (the "installed & initialized
80033
+ * on this host" signal). A stricter "is logged in" check would look for the auth
80034
+ * file instead (e.g. `~/.codex/auth.json`), but that yields false negatives for
80035
+ * users who authenticate via an API-key env var, so directory presence is the
80036
+ * pragmatic signal. Paths verified against each tool's docs (June 2026).
80037
+ */
80038
+ const isWin = process.platform === "win32";
80039
+ function isExecutableFile(p) {
80040
+ try {
80041
+ accessSync(p, isWin ? constants.F_OK : constants.X_OK);
80042
+ return true;
80043
+ } catch {
80044
+ return false;
80045
+ }
80046
+ }
80047
+ /**
80048
+ * Resolve a launcher command the way a shell would. A command containing a path
80049
+ * separator is treated as a literal path; a bare name is searched across `$PATH`
80050
+ * (trying `$PATHEXT` extensions on Windows).
80051
+ */
80052
+ function isCommandAvailable(command, env = process.env) {
80053
+ const exts = isWin ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")] : [""];
80054
+ if (command.includes("/") || isWin && command.includes("\\")) return exts.some((ext) => isExecutableFile(command + ext));
80055
+ return (env.PATH ?? "").split(delimiter).filter(Boolean).some((dir) => exts.some((ext) => isExecutableFile(join(dir, command) + ext)));
80056
+ }
80057
+ function home(env) {
80058
+ return (isWin ? env.USERPROFILE : env.HOME) || homedir();
80059
+ }
80060
+ function xdgConfigHome(env) {
80061
+ return env.XDG_CONFIG_HOME || join(home(env), ".config");
80062
+ }
80063
+ function xdgDataHome(env) {
80064
+ return env.XDG_DATA_HOME || join(home(env), ".local", "share");
80065
+ }
80066
+ function pathExists(p) {
80067
+ return !!p && existsSync(p);
80068
+ }
80069
+ /** True if any candidate path exists (undefined candidates are skipped). */
80070
+ function anyExists(...candidates) {
80071
+ return candidates.some(pathExists);
80072
+ }
80073
+ /**
80074
+ * Custom probes keyed by ACP-registry runtime id. Add an entry here whenever the
80075
+ * launcher-on-PATH check is too weak to mean "actually installed & set up".
80076
+ * Each tests for the tool's home/XDG config dir (or, where the dir is ambiguous,
80077
+ * a tool-specific file), honoring the documented env overrides.
80078
+ */
80079
+ const CUSTOM_PROBES = {
80080
+ "claude-acp": (env) => anyExists(join(home(env), ".claude"), join(home(env), ".claude.json")),
80081
+ "codex-acp": (env) => anyExists(env.CODEX_HOME, join(home(env), ".codex")),
80082
+ gemini: (env) => pathExists(join(home(env), ".gemini")),
80083
+ "qwen-code": (env) => pathExists(join(home(env), ".qwen")),
80084
+ "github-copilot-cli": (env) => anyExists(env.COPILOT_HOME, join(home(env), ".copilot")),
80085
+ cursor: (env) => anyExists(env.CURSOR_CONFIG_DIR ? join(env.CURSOR_CONFIG_DIR, "cli-config.json") : void 0, join(home(env), ".cursor", "cli-config.json")),
80086
+ opencode: (env) => anyExists(join(xdgConfigHome(env), "opencode"), join(xdgDataHome(env), "opencode", "auth.json")),
80087
+ goose: (env) => pathExists(join(xdgConfigHome(env), "goose")),
80088
+ "amp-acp": (env) => anyExists(env.AMP_SETTINGS_FILE, join(xdgConfigHome(env), "amp")),
80089
+ auggie: (env) => pathExists(join(home(env), ".augment")),
80090
+ cline: (env) => anyExists(env.CLINE_DATA_DIR, join(home(env), ".cline")),
80091
+ "grok-build": (env) => pathExists(join(home(env), ".grok")),
80092
+ kimi: (env) => anyExists(env.KIMI_CODE_HOME, join(home(env), ".kimi"), join(home(env), ".kimi-code")),
80093
+ "factory-droid": (env) => pathExists(join(home(env), ".factory")),
80094
+ devin: (env) => anyExists(join(xdgConfigHome(env), "devin"), join(xdgDataHome(env), "devin"))
80095
+ };
80096
+ /** Is this runtime actually usable on this host? */
80097
+ function isRuntimeAvailable(id, rt, env = process.env) {
80098
+ if (!isCommandAvailable(rt.command, env)) return false;
80099
+ const custom = CUSTOM_PROBES[id];
80100
+ return custom ? custom(env) : true;
80101
+ }
80102
+ /**
80103
+ * Filter a runtime map down to those installed on this host — what the daemon
80104
+ * should report to the Control Plane and is able to launch.
80105
+ */
80106
+ function installedRuntimes(runtimes, env = process.env) {
80107
+ const out = {};
80108
+ for (const [id, rt] of Object.entries(runtimes)) if (isRuntimeAvailable(id, rt, env)) out[id] = rt;
80109
+ return out;
80110
+ }
80111
+ //#endregion
80001
80112
  //#region src/log.ts
80002
80113
  const ORDER = {
80003
80114
  trace: 10,
@@ -80442,6 +80553,7 @@ var Daemon = class {
80442
80553
  log = makeLogger("info");
80443
80554
  root = "";
80444
80555
  runtimes = {};
80556
+ runtimeNames = {};
80445
80557
  cpClient;
80446
80558
  cpCrons;
80447
80559
  botUserIds = {};
@@ -80471,11 +80583,15 @@ var Daemon = class {
80471
80583
  for (const a of agents) this.agents.set(a.id, a);
80472
80584
  this.log.info(`loaded ${agents.length} agent(s) from ${this.agentsDir}${agents.length ? `: ${agents.map((a) => a.id).join(", ")}` : ""}`);
80473
80585
  this.root = root;
80474
- this.runtimes = await resolveRuntimes(cfg, root, {
80586
+ const resolvedRuntimes = await resolveRuntimes(cfg, root, {
80475
80587
  neededRuntimes: agents.map((a) => a.runtime),
80476
80588
  mode: "cache-first"
80477
80589
  });
80590
+ this.runtimes = installedRuntimes(resolvedRuntimes);
80591
+ this.runtimeNames = cachedRuntimeNames(root);
80478
80592
  this.log.info(`runtimes ready: ${Object.keys(this.runtimes).join(", ") || "(none)"}`);
80593
+ const skipped = Object.keys(resolvedRuntimes).filter((id) => !this.runtimes[id]);
80594
+ if (skipped.length) this.log.info(`runtimes not installed (skipped): ${skipped.join(", ")}`);
80479
80595
  this.store = new LocalStore(statePath(root));
80480
80596
  this.cpRouting = new CpRoutingLayer({
80481
80597
  load: () => {
@@ -80564,7 +80680,7 @@ var Daemon = class {
80564
80680
  if (this.opts.hostFactory) host = this.opts.hostFactory(agent, onUpdate);
80565
80681
  else {
80566
80682
  const runtime = this.runtimes[agent.runtime];
80567
- if (!runtime) throw new Error(`runtime "${agent.runtime}" not in config.runtimes or ACP registry`);
80683
+ if (!runtime) throw new Error(`runtime "${agent.runtime}" not available: not installed on this host, or absent from config.runtimes / the ACP registry`);
80568
80684
  host = new AcpHost(runtime, {
80569
80685
  onUpdate,
80570
80686
  env: agentChildEnv(agent)
@@ -80729,7 +80845,7 @@ var Daemon = class {
80729
80845
  maxAgents: this.cfg.limits.maxAgents,
80730
80846
  capabilities: () => ({
80731
80847
  platforms: ["slack"],
80732
- runtimes: Object.keys(this.runtimes),
80848
+ runtimes: Object.keys(this.runtimes).map((id) => this.runtimeNames[id] ?? id),
80733
80849
  acp: true,
80734
80850
  features: []
80735
80851
  }),