@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 +131 -15
- 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;
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
}),
|