@agentconnect.md/daemon 1.0.0-rc.10 → 1.0.0-rc.12
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 +121 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -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
|
-
|
|
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
|
|
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
|
}),
|