@agentconnect.md/daemon 1.0.0-rc.47 → 1.0.0-rc.49

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
@@ -3045,7 +3045,7 @@ var $ZodEncodeError = class extends Error {
3045
3045
  };
3046
3046
  (_a$2 = globalThis).__zod_globalConfig ?? (_a$2.__zod_globalConfig = {});
3047
3047
  const globalConfig = globalThis.__zod_globalConfig;
3048
- function config$1(newConfig) {
3048
+ function config(newConfig) {
3049
3049
  if (newConfig) Object.assign(globalConfig, newConfig);
3050
3050
  return globalConfig;
3051
3051
  }
@@ -3437,7 +3437,7 @@ const _parse = (_Err) => (schema, value, _ctx, _params) => {
3437
3437
  }, ctx);
3438
3438
  if (result instanceof Promise) throw new $ZodAsyncError();
3439
3439
  if (result.issues.length) {
3440
- const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config$1())));
3440
+ const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
3441
3441
  captureStackTrace(e, _params?.callee);
3442
3442
  throw e;
3443
3443
  }
@@ -3454,7 +3454,7 @@ const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
3454
3454
  }, ctx);
3455
3455
  if (result instanceof Promise) result = await result;
3456
3456
  if (result.issues.length) {
3457
- const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config$1())));
3457
+ const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
3458
3458
  captureStackTrace(e, params?.callee);
3459
3459
  throw e;
3460
3460
  }
@@ -3472,7 +3472,7 @@ const _safeParse = (_Err) => (schema, value, _ctx) => {
3472
3472
  if (result instanceof Promise) throw new $ZodAsyncError();
3473
3473
  return result.issues.length ? {
3474
3474
  success: false,
3475
- error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config$1())))
3475
+ error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
3476
3476
  } : {
3477
3477
  success: true,
3478
3478
  data: result.value
@@ -3491,7 +3491,7 @@ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
3491
3491
  if (result instanceof Promise) result = await result;
3492
3492
  return result.issues.length ? {
3493
3493
  success: false,
3494
- error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config$1())))
3494
+ error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
3495
3495
  } : {
3496
3496
  success: true,
3497
3497
  data: result.value
@@ -4732,7 +4732,7 @@ function handleUnionResults(results, final, inst, ctx) {
4732
4732
  code: "invalid_union",
4733
4733
  input: final.value,
4734
4734
  inst,
4735
- errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config$1())))
4735
+ errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
4736
4736
  });
4737
4737
  return final;
4738
4738
  }
@@ -4957,7 +4957,7 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
4957
4957
  payload.issues.push({
4958
4958
  code: "invalid_key",
4959
4959
  origin: "record",
4960
- issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config$1())),
4960
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
4961
4961
  input: key,
4962
4962
  path: [key],
4963
4963
  inst
@@ -5012,7 +5012,7 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
5012
5012
  else payload.issues.push({
5013
5013
  code: "invalid_key",
5014
5014
  origin: "record",
5015
- issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config$1())),
5015
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
5016
5016
  input: key,
5017
5017
  path: [key],
5018
5018
  inst
@@ -5209,7 +5209,7 @@ const $ZodCatch = /*@__PURE__*/ $constructor("$ZodCatch", (inst, def) => {
5209
5209
  if (result.issues.length) {
5210
5210
  payload.value = def.catchValue({
5211
5211
  ...payload,
5212
- error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config$1())) },
5212
+ error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },
5213
5213
  input: payload.value
5214
5214
  });
5215
5215
  payload.issues = [];
@@ -5221,7 +5221,7 @@ const $ZodCatch = /*@__PURE__*/ $constructor("$ZodCatch", (inst, def) => {
5221
5221
  if (result.issues.length) {
5222
5222
  payload.value = def.catchValue({
5223
5223
  ...payload,
5224
- error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config$1())) },
5224
+ error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },
5225
5225
  input: payload.value
5226
5226
  });
5227
5227
  payload.issues = [];
@@ -16889,6 +16889,128 @@ AGENT_METHODS.session_cancel, AGENT_METHODS.nes_accept, AGENT_METHODS.nes_reject
16889
16889
  CLIENT_METHODS.session_request_permission, CLIENT_METHODS.fs_write_text_file, CLIENT_METHODS.fs_read_text_file, CLIENT_METHODS.terminal_create, CLIENT_METHODS.terminal_output, CLIENT_METHODS.terminal_release, CLIENT_METHODS.terminal_wait_for_exit, CLIENT_METHODS.terminal_kill, CLIENT_METHODS.elicitation_create;
16890
16890
  CLIENT_METHODS.session_update, CLIENT_METHODS.elicitation_complete;
16891
16891
  //#endregion
16892
+ //#region src/runtimes/probe.ts
16893
+ /**
16894
+ * Host-availability probing for runtimes.
16895
+ *
16896
+ * `resolveRuntimes()` produces a runtime for every ACP-registry entry that has a
16897
+ * distribution for this platform — but that says nothing about whether the tool
16898
+ * is actually runnable *here*. This module answers that question so the daemon
16899
+ * only advertises genuinely-installed runtimes in `RegisterReq.capabilities`.
16900
+ *
16901
+ * Two layers:
16902
+ * 1. A generic launcher check — is the runtime's `command` resolvable? For a
16903
+ * runtime distributed as a real binary (`command` is the tool's own
16904
+ * executable) this is a meaningful signal: the binary on `$PATH` means it's
16905
+ * installed.
16906
+ * 2. For runtimes distributed via a package launcher (`npx`/`uvx`), (1) is
16907
+ * meaningless — the launcher fetches on demand, so "npx exists" is true on
16908
+ * virtually every dev host and tells us nothing about the wrapped agent. For
16909
+ * those we REQUIRE a per-runtime custom probe (`CUSTOM_PROBES`) that looks for
16910
+ * the wrapped CLI's own config/state dir, which appears once the user has
16911
+ * installed and run/initialized it. A launcher-distributed runtime with no
16912
+ * probe is treated as NOT installed — otherwise every npx/uvx agent in the
16913
+ * ACP registry would be falsely advertised just because `npx` is on `$PATH`.
16914
+ *
16915
+ * The custom probes test for the config *directory* (the "installed & initialized
16916
+ * on this host" signal). A stricter "is logged in" check would look for the auth
16917
+ * file instead (e.g. `~/.codex/auth.json`), but that yields false negatives for
16918
+ * users who authenticate via an API-key env var, so directory presence is the
16919
+ * pragmatic signal. Paths verified against each tool's docs (June 2026).
16920
+ */
16921
+ const isWin = process.platform === "win32";
16922
+ function isExecutableFile(p) {
16923
+ try {
16924
+ accessSync(p, isWin ? constants.F_OK : constants.X_OK);
16925
+ return true;
16926
+ } catch {
16927
+ return false;
16928
+ }
16929
+ }
16930
+ /**
16931
+ * Resolve a launcher command the way a shell would. A command containing a path
16932
+ * separator is treated as a literal path; a bare name is searched across `$PATH`
16933
+ * (trying `$PATHEXT` extensions on Windows).
16934
+ */
16935
+ function isCommandAvailable(command, env = process.env) {
16936
+ return resolveCommandPath(command, env) !== void 0;
16937
+ }
16938
+ /**
16939
+ * Resolve a launcher/command to its absolute path the way a shell would — a path
16940
+ * containing a separator is checked literally; a bare name is searched across
16941
+ * `$PATH` (trying `$PATHEXT` extensions on Windows). Returns undefined if not found.
16942
+ */
16943
+ function resolveCommandPath(command, env = process.env) {
16944
+ const exts = isWin ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")] : [""];
16945
+ if (command.includes("/") || isWin && command.includes("\\")) return exts.map((ext) => command + ext).find(isExecutableFile);
16946
+ const dirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
16947
+ for (const dir of dirs) for (const ext of exts) {
16948
+ const p = join(dir, command) + ext;
16949
+ if (isExecutableFile(p)) return p;
16950
+ }
16951
+ }
16952
+ function home(env) {
16953
+ return (isWin ? env.USERPROFILE : env.HOME) || homedir();
16954
+ }
16955
+ function xdgConfigHome(env) {
16956
+ return env.XDG_CONFIG_HOME || join(home(env), ".config");
16957
+ }
16958
+ function xdgDataHome(env) {
16959
+ return env.XDG_DATA_HOME || join(home(env), ".local", "share");
16960
+ }
16961
+ function pathExists(p) {
16962
+ return !!p && existsSync(p);
16963
+ }
16964
+ /** True if any candidate path exists (undefined candidates are skipped). */
16965
+ function anyExists(...candidates) {
16966
+ return candidates.some(pathExists);
16967
+ }
16968
+ /**
16969
+ * Custom probes keyed by ACP-registry runtime id. Add an entry here whenever the
16970
+ * launcher-on-PATH check is too weak to mean "actually installed & set up".
16971
+ * Each tests for the tool's home/XDG config dir (or, where the dir is ambiguous,
16972
+ * a tool-specific file), honoring the documented env overrides.
16973
+ */
16974
+ const CUSTOM_PROBES = {
16975
+ "claude-acp": (env) => anyExists(join(home(env), ".claude"), join(home(env), ".claude.json")),
16976
+ "codex-acp": (env) => anyExists(env.CODEX_HOME, join(home(env), ".codex")),
16977
+ gemini: (env) => pathExists(join(home(env), ".gemini")),
16978
+ "qwen-code": (env) => pathExists(join(home(env), ".qwen")),
16979
+ "github-copilot-cli": (env) => anyExists(env.COPILOT_HOME, join(home(env), ".copilot")),
16980
+ cursor: (env) => anyExists(env.CURSOR_CONFIG_DIR ? join(env.CURSOR_CONFIG_DIR, "cli-config.json") : void 0, join(home(env), ".cursor", "cli-config.json")),
16981
+ opencode: (env) => anyExists(join(xdgConfigHome(env), "opencode"), join(xdgDataHome(env), "opencode", "auth.json")),
16982
+ goose: (env) => pathExists(join(xdgConfigHome(env), "goose")),
16983
+ "amp-acp": (env) => anyExists(env.AMP_SETTINGS_FILE, join(xdgConfigHome(env), "amp")),
16984
+ auggie: (env) => pathExists(join(home(env), ".augment")),
16985
+ cline: (env) => anyExists(env.CLINE_DATA_DIR, join(home(env), ".cline")),
16986
+ "grok-build": (env) => pathExists(join(home(env), ".grok")),
16987
+ kimi: (env) => anyExists(env.KIMI_CODE_HOME, join(home(env), ".kimi"), join(home(env), ".kimi-code")),
16988
+ "factory-droid": (env) => pathExists(join(home(env), ".factory")),
16989
+ devin: (env) => anyExists(join(xdgConfigHome(env), "devin"), join(xdgDataHome(env), "devin"))
16990
+ };
16991
+ /**
16992
+ * Package launchers that fetch-and-run on demand. When a runtime is launched via
16993
+ * one of these, the launcher being on `$PATH` says nothing about whether the
16994
+ * wrapped agent is installed, so such runtimes require a custom probe to count.
16995
+ */
16996
+ const PACKAGE_LAUNCHERS = /* @__PURE__ */ new Set(["npx", "uvx"]);
16997
+ /** Is this runtime actually usable on this host? */
16998
+ function isRuntimeAvailable(id, rt, env = process.env) {
16999
+ if (!isCommandAvailable(rt.command, env)) return false;
17000
+ const custom = CUSTOM_PROBES[id];
17001
+ if (custom) return custom(env);
17002
+ return !PACKAGE_LAUNCHERS.has(rt.command);
17003
+ }
17004
+ /**
17005
+ * Filter a runtime map down to those installed on this host — what the daemon
17006
+ * should report to the Control Plane and is able to launch.
17007
+ */
17008
+ function installedRuntimes(runtimes, env = process.env) {
17009
+ const out = {};
17010
+ for (const [id, rt] of Object.entries(runtimes)) if (isRuntimeAvailable(id, rt, env)) out[id] = rt;
17011
+ return out;
17012
+ }
17013
+ //#endregion
16892
17014
  //#region src/acp/acp-host.ts
16893
17015
  const PROTOCOL_VERSION = 1;
16894
17016
  /**
@@ -16920,19 +17042,32 @@ var AcpHost = class {
16920
17042
  this.runtime = runtime;
16921
17043
  this.opts = opts;
16922
17044
  }
17045
+ /** A Claude Code runtime (its command/args reference `claude`) — these embed the
17046
+ * @anthropic-ai/claude-agent-sdk, which needs a Claude Code executable. */
17047
+ isClaudeRuntime() {
17048
+ return [this.runtime.command, ...this.runtime.args].join(" ").toLowerCase().includes("claude");
17049
+ }
16923
17050
  async start() {
16924
17051
  if (this.child) throw new Error("AcpHost: already started");
17052
+ const env = {
17053
+ ...process.env,
17054
+ ...Object.fromEntries(this.runtime.env.map((e) => [e.name, e.value])),
17055
+ ...this.opts.env ?? {}
17056
+ };
17057
+ if (!env.CLAUDE_CODE_EXECUTABLE && this.isClaudeRuntime()) {
17058
+ const claude = resolveCommandPath("claude", env);
17059
+ if (claude) {
17060
+ env.CLAUDE_CODE_EXECUTABLE = claude;
17061
+ this.opts.log?.info(`acp: CLAUDE_CODE_EXECUTABLE not set — using claude on PATH (${claude})`);
17062
+ }
17063
+ }
16925
17064
  const child = spawn(this.runtime.command, this.runtime.args, {
16926
17065
  stdio: [
16927
17066
  "pipe",
16928
17067
  "pipe",
16929
17068
  "inherit"
16930
17069
  ],
16931
- env: {
16932
- ...process.env,
16933
- ...Object.fromEntries(this.runtime.env.map((e) => [e.name, e.value])),
16934
- ...this.opts.env ?? {}
16935
- }
17070
+ env
16936
17071
  });
16937
17072
  this.child = child;
16938
17073
  if (!child.stdin || !child.stdout) throw new Error("AcpHost: subprocess stdin/stdout are not piped");
@@ -17398,6 +17533,61 @@ const AgentScopeDenied = object({
17398
17533
  capability: string()
17399
17534
  });
17400
17535
  //#endregion
17536
+ //#region ../protocol/dist/frames/integration.js
17537
+ /**
17538
+ * Platform integration distribution (C→D) — the Slack "install" flow.
17539
+ *
17540
+ * The Control Plane is the source of truth for platform integrations and pushes
17541
+ * them to the daemon that owns the integration's agent (`integration/upsert`, and
17542
+ * the reconcile snapshot `RegisterOk.integrations[]`). The daemon opens the Socket
17543
+ * Mode connection from the delivered config (see slack/connection.ts).
17544
+ *
17545
+ * SECURITY: `integration/upsert` and `RegisterOk.integrations[]` carry PLAINTEXT
17546
+ * platform tokens (botToken/appToken). These payloads MUST NEVER be logged — no
17547
+ * body dump on decode error, no register/ok snapshot debug dump.
17548
+ *
17549
+ * `signingSecret` is intentionally absent: Socket Mode authenticates with the
17550
+ * app-level token, so the daemon never needs a signing secret.
17551
+ */
17552
+ /** Trigger match — mirrors the daemon BindRuleConfig.match (agents/agent-schema.ts). */
17553
+ const BindMatch = discriminatedUnion("kind", [
17554
+ object({ kind: literal("mention") }),
17555
+ object({ kind: literal("dm") }),
17556
+ object({
17557
+ kind: literal("keyword"),
17558
+ value: string()
17559
+ }),
17560
+ object({ kind: literal("auto") })
17561
+ ]);
17562
+ /** One channel/thread trigger binding — mirrors the daemon BindRuleConfig. */
17563
+ const IntegrationBindRule = object({
17564
+ channel: string().optional(),
17565
+ thread: string().optional(),
17566
+ match: BindMatch
17567
+ });
17568
+ /** The Slack config the daemon needs to open + route a socket (no signingSecret). */
17569
+ const IntegrationSlackConfig = object({
17570
+ botToken: string(),
17571
+ appToken: string(),
17572
+ allowedUserIds: array(string()).default([]),
17573
+ notificationChannelId: string().optional(),
17574
+ bindRules: array(IntegrationBindRule).default([])
17575
+ });
17576
+ /**
17577
+ * One platform integration, owned by exactly one agent. Also the element type of
17578
+ * `RegisterOk.integrations[]` (the per-daemon reconcile set).
17579
+ */
17580
+ const IntegrationSpec = object({
17581
+ integrationId: string().uuid(),
17582
+ agentId: string().uuid(),
17583
+ platform: literal("slack"),
17584
+ slack: IntegrationSlackConfig
17585
+ });
17586
+ /** C→D EVT — install/update an integration on the owning agent's daemon. */
17587
+ const IntegrationUpsert = IntegrationSpec;
17588
+ /** C→D EVT — remove an integration from the daemon. */
17589
+ const IntegrationRemove = object({ integrationId: string().uuid() });
17590
+ //#endregion
17401
17591
  //#region ../protocol/dist/frames/register.js
17402
17592
  /**
17403
17593
  * Capability upload + the reconcile snapshot — protocol §3.3.
@@ -17426,6 +17616,7 @@ const RegisterOk = object({
17426
17616
  assignments: array(RouteAssign),
17427
17617
  agents: array(AgentSpec.extend({ agentId: string().uuid() })).default([]),
17428
17618
  crons: array(CronUpsert),
17619
+ integrations: array(IntegrationSpec).default([]),
17429
17620
  leases: array(SecretsGrant),
17430
17621
  drop: object({
17431
17622
  assignments: array(string()),
@@ -17527,6 +17718,8 @@ const FRAME_SCHEMAS = {
17527
17718
  "drain/done": DrainDone,
17528
17719
  "cron/upsert": CronUpsert,
17529
17720
  "cron/remove": CronRemove,
17721
+ "integration/upsert": IntegrationUpsert,
17722
+ "integration/remove": IntegrationRemove,
17530
17723
  "secrets/request": SecretsRequest,
17531
17724
  "secrets/grant": SecretsGrant,
17532
17725
  "secrets/renew": SecretsRenew,
@@ -17641,6 +17834,8 @@ discriminatedUnion("type", [
17641
17834
  frame("drain/done", FRAME_SCHEMAS["drain/done"]),
17642
17835
  frame("cron/upsert", FRAME_SCHEMAS["cron/upsert"]),
17643
17836
  frame("cron/remove", FRAME_SCHEMAS["cron/remove"]),
17837
+ frame("integration/upsert", FRAME_SCHEMAS["integration/upsert"]),
17838
+ frame("integration/remove", FRAME_SCHEMAS["integration/remove"]),
17644
17839
  frame("secrets/request", FRAME_SCHEMAS["secrets/request"]),
17645
17840
  frame("secrets/grant", FRAME_SCHEMAS["secrets/grant"]),
17646
17841
  frame("secrets/renew", FRAME_SCHEMAS["secrets/renew"]),
@@ -81451,116 +81646,6 @@ var Scheduler = class {
81451
81646
  }
81452
81647
  };
81453
81648
  //#endregion
81454
- //#region src/runtimes/probe.ts
81455
- /**
81456
- * Host-availability probing for runtimes.
81457
- *
81458
- * `resolveRuntimes()` produces a runtime for every ACP-registry entry that has a
81459
- * distribution for this platform — but that says nothing about whether the tool
81460
- * is actually runnable *here*. This module answers that question so the daemon
81461
- * only advertises genuinely-installed runtimes in `RegisterReq.capabilities`.
81462
- *
81463
- * Two layers:
81464
- * 1. A generic launcher check — is the runtime's `command` resolvable? For a
81465
- * runtime distributed as a real binary (`command` is the tool's own
81466
- * executable) this is a meaningful signal: the binary on `$PATH` means it's
81467
- * installed.
81468
- * 2. For runtimes distributed via a package launcher (`npx`/`uvx`), (1) is
81469
- * meaningless — the launcher fetches on demand, so "npx exists" is true on
81470
- * virtually every dev host and tells us nothing about the wrapped agent. For
81471
- * those we REQUIRE a per-runtime custom probe (`CUSTOM_PROBES`) that looks for
81472
- * the wrapped CLI's own config/state dir, which appears once the user has
81473
- * installed and run/initialized it. A launcher-distributed runtime with no
81474
- * probe is treated as NOT installed — otherwise every npx/uvx agent in the
81475
- * ACP registry would be falsely advertised just because `npx` is on `$PATH`.
81476
- *
81477
- * The custom probes test for the config *directory* (the "installed & initialized
81478
- * on this host" signal). A stricter "is logged in" check would look for the auth
81479
- * file instead (e.g. `~/.codex/auth.json`), but that yields false negatives for
81480
- * users who authenticate via an API-key env var, so directory presence is the
81481
- * pragmatic signal. Paths verified against each tool's docs (June 2026).
81482
- */
81483
- const isWin = process.platform === "win32";
81484
- function isExecutableFile(p) {
81485
- try {
81486
- accessSync(p, isWin ? constants.F_OK : constants.X_OK);
81487
- return true;
81488
- } catch {
81489
- return false;
81490
- }
81491
- }
81492
- /**
81493
- * Resolve a launcher command the way a shell would. A command containing a path
81494
- * separator is treated as a literal path; a bare name is searched across `$PATH`
81495
- * (trying `$PATHEXT` extensions on Windows).
81496
- */
81497
- function isCommandAvailable(command, env = process.env) {
81498
- const exts = isWin ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")] : [""];
81499
- if (command.includes("/") || isWin && command.includes("\\")) return exts.some((ext) => isExecutableFile(command + ext));
81500
- return (env.PATH ?? "").split(delimiter).filter(Boolean).some((dir) => exts.some((ext) => isExecutableFile(join(dir, command) + ext)));
81501
- }
81502
- function home(env) {
81503
- return (isWin ? env.USERPROFILE : env.HOME) || homedir();
81504
- }
81505
- function xdgConfigHome(env) {
81506
- return env.XDG_CONFIG_HOME || join(home(env), ".config");
81507
- }
81508
- function xdgDataHome(env) {
81509
- return env.XDG_DATA_HOME || join(home(env), ".local", "share");
81510
- }
81511
- function pathExists(p) {
81512
- return !!p && existsSync(p);
81513
- }
81514
- /** True if any candidate path exists (undefined candidates are skipped). */
81515
- function anyExists(...candidates) {
81516
- return candidates.some(pathExists);
81517
- }
81518
- /**
81519
- * Custom probes keyed by ACP-registry runtime id. Add an entry here whenever the
81520
- * launcher-on-PATH check is too weak to mean "actually installed & set up".
81521
- * Each tests for the tool's home/XDG config dir (or, where the dir is ambiguous,
81522
- * a tool-specific file), honoring the documented env overrides.
81523
- */
81524
- const CUSTOM_PROBES = {
81525
- "claude-acp": (env) => anyExists(join(home(env), ".claude"), join(home(env), ".claude.json")),
81526
- "codex-acp": (env) => anyExists(env.CODEX_HOME, join(home(env), ".codex")),
81527
- gemini: (env) => pathExists(join(home(env), ".gemini")),
81528
- "qwen-code": (env) => pathExists(join(home(env), ".qwen")),
81529
- "github-copilot-cli": (env) => anyExists(env.COPILOT_HOME, join(home(env), ".copilot")),
81530
- cursor: (env) => anyExists(env.CURSOR_CONFIG_DIR ? join(env.CURSOR_CONFIG_DIR, "cli-config.json") : void 0, join(home(env), ".cursor", "cli-config.json")),
81531
- opencode: (env) => anyExists(join(xdgConfigHome(env), "opencode"), join(xdgDataHome(env), "opencode", "auth.json")),
81532
- goose: (env) => pathExists(join(xdgConfigHome(env), "goose")),
81533
- "amp-acp": (env) => anyExists(env.AMP_SETTINGS_FILE, join(xdgConfigHome(env), "amp")),
81534
- auggie: (env) => pathExists(join(home(env), ".augment")),
81535
- cline: (env) => anyExists(env.CLINE_DATA_DIR, join(home(env), ".cline")),
81536
- "grok-build": (env) => pathExists(join(home(env), ".grok")),
81537
- kimi: (env) => anyExists(env.KIMI_CODE_HOME, join(home(env), ".kimi"), join(home(env), ".kimi-code")),
81538
- "factory-droid": (env) => pathExists(join(home(env), ".factory")),
81539
- devin: (env) => anyExists(join(xdgConfigHome(env), "devin"), join(xdgDataHome(env), "devin"))
81540
- };
81541
- /**
81542
- * Package launchers that fetch-and-run on demand. When a runtime is launched via
81543
- * one of these, the launcher being on `$PATH` says nothing about whether the
81544
- * wrapped agent is installed, so such runtimes require a custom probe to count.
81545
- */
81546
- const PACKAGE_LAUNCHERS = /* @__PURE__ */ new Set(["npx", "uvx"]);
81547
- /** Is this runtime actually usable on this host? */
81548
- function isRuntimeAvailable(id, rt, env = process.env) {
81549
- if (!isCommandAvailable(rt.command, env)) return false;
81550
- const custom = CUSTOM_PROBES[id];
81551
- if (custom) return custom(env);
81552
- return !PACKAGE_LAUNCHERS.has(rt.command);
81553
- }
81554
- /**
81555
- * Filter a runtime map down to those installed on this host — what the daemon
81556
- * should report to the Control Plane and is able to launch.
81557
- */
81558
- function installedRuntimes(runtimes, env = process.env) {
81559
- const out = {};
81560
- for (const [id, rt] of Object.entries(runtimes)) if (isRuntimeAvailable(id, rt, env)) out[id] = rt;
81561
- return out;
81562
- }
81563
- //#endregion
81564
81649
  //#region src/runtimes/runtime-prober.ts
81565
81650
  const DEFAULT_TIMEOUT_MS = 3e4;
81566
81651
  const DEFAULT_CONCURRENCY = 3;
@@ -81963,6 +82048,12 @@ var CpClient = class {
81963
82048
  case "agent/remove":
81964
82049
  this.deps.configApply.applyAgentRemove(frame.payload.agentId);
81965
82050
  return;
82051
+ case "integration/upsert":
82052
+ this.deps.configApply.applyIntegrationUpsert(frame.payload);
82053
+ return;
82054
+ case "integration/remove":
82055
+ this.deps.configApply.applyIntegrationRemove(frame.payload.integrationId);
82056
+ return;
81966
82057
  case "agent/launch": {
81967
82058
  const launch = frame.payload;
81968
82059
  this.deps.configApply.applyAgentLaunch(launch).then((launched) => this.reply(frame, "agent/launched", launched)).catch((err) => this.sendError(frame.id, "INTERNAL", `agent/launch failed: ${err.message}`, false));
@@ -82259,6 +82350,59 @@ var CpAgentRegistry = class {
82259
82350
  }
82260
82351
  };
82261
82352
  //#endregion
82353
+ //#region src/cp/cp-integration-registry.ts
82354
+ /** Map the wire spec to the daemon's `Integration` shape (agent-schema). */
82355
+ function toIntegration(spec) {
82356
+ return {
82357
+ id: spec.integrationId,
82358
+ platform: "slack",
82359
+ slack: {
82360
+ botToken: spec.slack.botToken,
82361
+ appToken: spec.slack.appToken,
82362
+ allowedUserIds: spec.slack.allowedUserIds,
82363
+ ...spec.slack.notificationChannelId ? { notificationChannelId: spec.slack.notificationChannelId } : {},
82364
+ bindRules: spec.slack.bindRules
82365
+ }
82366
+ };
82367
+ }
82368
+ var CpIntegrationRegistry = class {
82369
+ onChange;
82370
+ byId = /* @__PURE__ */ new Map();
82371
+ constructor(onChange) {
82372
+ this.onChange = onChange;
82373
+ }
82374
+ /** Add or replace one CP integration (integration/upsert EVT). */
82375
+ upsert(spec) {
82376
+ this.byId.set(spec.integrationId, spec);
82377
+ this.onChange();
82378
+ }
82379
+ /** Drop one CP integration (integration/remove EVT). */
82380
+ remove(integrationId) {
82381
+ if (this.byId.delete(integrationId)) this.onChange();
82382
+ }
82383
+ /**
82384
+ * Replace the whole CP-owned set from the `register/ok` reconcile roster (the CP
82385
+ * is authoritative and the roster is already filtered to THIS daemon).
82386
+ */
82387
+ converge(specs) {
82388
+ this.byId = new Map((specs ?? []).map((s) => [s.integrationId, s]));
82389
+ this.onChange();
82390
+ }
82391
+ /** CP integrations grouped by owning agentId, as daemon `Integration[]`. */
82392
+ byAgent() {
82393
+ const out = /* @__PURE__ */ new Map();
82394
+ for (const spec of this.byId.values()) {
82395
+ const list = out.get(spec.agentId) ?? [];
82396
+ list.push(toIntegration(spec));
82397
+ out.set(spec.agentId, list);
82398
+ }
82399
+ return out;
82400
+ }
82401
+ get size() {
82402
+ return this.byId.size;
82403
+ }
82404
+ };
82405
+ //#endregion
82262
82406
  //#region src/cp/config-apply.ts
82263
82407
  const LOG_LEVELS = /* @__PURE__ */ new Set([
82264
82408
  "trace",
@@ -82399,6 +82543,8 @@ var Daemon = class Daemon {
82399
82543
  cpClient;
82400
82544
  cpCrons;
82401
82545
  cpAgents;
82546
+ cpIntegrations;
82547
+ warnedOrphanIntegrations = /* @__PURE__ */ new Set();
82402
82548
  botUserIds = {};
82403
82549
  cpRouting;
82404
82550
  clock;
@@ -82461,6 +82607,7 @@ var Daemon = class Daemon {
82461
82607
  knownRuntimes: Object.keys(this.runtimes),
82462
82608
  warn: (m) => this.log.warn(m)
82463
82609
  }, () => void this.reconcile().catch((err) => this.log.error(`cp: agent reconcile failed: ${err.stack ?? err}`)));
82610
+ this.cpIntegrations = new CpIntegrationRegistry(() => void this.reconcile().catch((err) => this.log.error(`cp: integration reconcile failed: ${err.stack ?? err}`)));
82464
82611
  for (const a of this.effectiveAgents()) {
82465
82612
  this.agents.set(a.id, a);
82466
82613
  this.prefetchClone(a);
@@ -82559,13 +82706,50 @@ var Daemon = class Daemon {
82559
82706
  return this.opts.agentName ? [selectAgent(this.agentsDir, this.opts.agentName)] : loadAgents(this.agentsDir);
82560
82707
  }
82561
82708
  /**
82562
- * Effective agent set = the on-disk `agent.json` files verbatim. The CP spec is
82563
- * no longer overlaid in memory — `agent/upsert` writes it straight to disk (see
82564
- * cp/cp-agent-registry.ts → agents/write-agent.ts), so the loaded file already
82565
- * carries the CP-owned fields.
82709
+ * Effective agent set = the on-disk `agent.json` files, with the CP-owned
82710
+ * integrations overlaid in memory. Agent SPECS are no longer overlaid — `agent/
82711
+ * upsert` writes those straight to disk (cp/cp-agent-registry.ts). INTEGRATIONS,
82712
+ * however, carry plaintext tokens and are kept in memory only (cp/cp-integration-
82713
+ * registry.ts); they are merged onto the matching file agent's `integrations[]`
82714
+ * HERE — the single seam so consolidate, routing rules, tool injection, and reply
82715
+ * routing (all of which read `agent.integrations`) see one merged view.
82566
82716
  */
82567
82717
  effectiveAgents() {
82568
- return [...this.fileAgents.values()];
82718
+ const files = [...this.fileAgents.values()];
82719
+ const cpByAgent = this.cpIntegrations?.byAgent();
82720
+ if (!cpByAgent || cpByAgent.size === 0) return files;
82721
+ return files.map((a) => {
82722
+ const cp = cpByAgent.get(a.id);
82723
+ if (!cp || cp.length === 0) return a;
82724
+ const byId = new Map(a.integrations.map((i) => [i.id, i]));
82725
+ for (const i of cp) byId.set(i.id, i);
82726
+ return {
82727
+ ...a,
82728
+ integrations: [...byId.values()]
82729
+ };
82730
+ });
82731
+ }
82732
+ /**
82733
+ * Warn (once per orphan) about CP integrations whose owning agent is absent /
82734
+ * inactive on disk: they are held in the registry but overlaid onto no agent, so
82735
+ * no socket opens. Self-heals when the agent appears (a later reconcile connects
82736
+ * it). Logs integrationId + agentId ONLY — never the token material.
82737
+ */
82738
+ warnOrphanIntegrations() {
82739
+ const cpByAgent = this.cpIntegrations?.byAgent();
82740
+ if (!cpByAgent) return;
82741
+ const live = /* @__PURE__ */ new Set();
82742
+ for (const [agentId, ints] of cpByAgent) {
82743
+ if (this.fileAgents.has(agentId)) continue;
82744
+ for (const int of ints) {
82745
+ live.add(int.id);
82746
+ if (!this.warnedOrphanIntegrations.has(int.id)) {
82747
+ this.warnedOrphanIntegrations.add(int.id);
82748
+ this.log.warn(`slack: integration ${int.id} references missing/inactive agent ${agentId} — not connected`);
82749
+ }
82750
+ }
82751
+ }
82752
+ for (const id of this.warnedOrphanIntegrations) if (!live.has(id)) this.warnedOrphanIntegrations.delete(id);
82569
82753
  }
82570
82754
  /**
82571
82755
  * Converge the running set to the desired effective agents. Driven by both the
@@ -82599,6 +82783,7 @@ var Daemon = class Daemon {
82599
82783
  async runReconcile() {
82600
82784
  const files = this.loadAgentList();
82601
82785
  this.fileAgents = new Map(files.map((a) => [a.id, a]));
82786
+ this.warnOrphanIntegrations();
82602
82787
  const desired = this.effectiveAgents();
82603
82788
  const { toStart, toStop, toChange } = diffAgents(desired, this.agents);
82604
82789
  if (toStart.length || toStop.length || toChange.length) this.log.info(`reconcile: ${desired.length} desired agent(s) from ${this.agentsDir}; start=[${toStart.map((a) => a.id).join(", ")}] stop=[${toStop.join(", ")}] change=[${toChange.map((c) => {
@@ -83250,6 +83435,7 @@ var Daemon = class Daemon {
83250
83435
  applyReconcileSnapshot: (snap) => {
83251
83436
  this.cpCrons?.converge(snap.crons);
83252
83437
  this.cpAgents?.converge(snap.agents);
83438
+ this.cpIntegrations?.converge(snap.integrations);
83253
83439
  this.cpRouting?.converge({
83254
83440
  routingEpoch: snap.routingEpoch,
83255
83441
  assignments: snap.assignments,
@@ -83258,9 +83444,12 @@ var Daemon = class Daemon {
83258
83444
  if (snap.leases.length) this.log.debug(`cp: ${snap.leases.length} lease(s) noted (secrets handled later)`);
83259
83445
  if (snap.assignments.length) this.log.debug(`cp: converged ${snap.assignments.length} assignment(s)`);
83260
83446
  if (snap.agents.length) this.log.debug(`cp: converged ${snap.agents.length} agent spec(s)`);
83447
+ if (snap.integrations?.length) this.log.debug(`cp: converged ${snap.integrations.length} integration(s)`);
83261
83448
  },
83262
83449
  applyAgentUpsert: (agentId, spec) => this.cpAgents?.upsert(agentId, spec),
83263
83450
  applyAgentRemove: (agentId) => this.cpAgents?.remove(agentId),
83451
+ applyIntegrationUpsert: (spec) => this.cpIntegrations?.upsert(spec),
83452
+ applyIntegrationRemove: (integrationId) => this.cpIntegrations?.remove(integrationId),
83264
83453
  upsertCron: (cron) => this.cpCrons.upsert(cron),
83265
83454
  removeCron: (cronId) => this.cpCrons.remove(cronId),
83266
83455
  applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),
@@ -83670,6 +83859,9 @@ program.name("agentconnect").description("AgentConnect daemon — edge message +
83670
83859
  program.option("--config <path>", "path to config.json (default ~/.agentconnect/config.json)").option("--root <dir>", "override ~/.agentconnect root directory").option("--cp-url <url>", "override controlPlane.url").option("--cp-key <key>", "override controlPlane.key (the CP API key)").option("--no-cp", "run fully local, do not connect to the Control Plane").option("--daemon-id <id>", "override daemon identity").option("--log-level <level>", "trace|debug|info|warn|error").option("--agents-dir <dir>", "override agents directory").option("--max-agents <n>", "max agents this daemon advertises / enforces").option("--dry-run", "load + validate config and print the reconcile plan, then exit").option("--agent <name>", "select a single agent by id (run/chat)");
83671
83860
  program.command("run").description("Run the daemon in the foreground").action(async () => {
83672
83861
  const opts = program.opts();
83862
+ process.on("unhandledRejection", (reason) => {
83863
+ console.error(`agentconnect: unhandled rejection (continuing): ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}`);
83864
+ });
83673
83865
  let lock;
83674
83866
  try {
83675
83867
  lock = acquireSingletonLock(resolveRoot(opts.root));
@@ -83700,7 +83892,7 @@ program.command("run").description("Run the daemon in the foreground").action(as
83700
83892
  }
83701
83893
  });
83702
83894
  program.command("mcp-bridge", { hidden: true }).description("internal: stdio MCP bridge to the running daemon").action(async () => {
83703
- const { runBridge } = await import("./bridge-hZMpl3k6.js");
83895
+ const { runBridge } = await import("./bridge-NDvTO1zB.js");
83704
83896
  await runBridge();
83705
83897
  });
83706
83898
  const controller = () => resolveController({ root: program.opts().root });
@@ -83802,20 +83994,20 @@ program.command("login").description("Interactive onboarding: test the Control P
83802
83994
  process.exit(1);
83803
83995
  }
83804
83996
  });
83805
- const config = program.command("config").description("Read / write / validate config.json");
83806
- const todo = (name) => () => {
83807
- console.log(`agentconnect: ${name} (not yet implemented)`);
83808
- };
83809
- config.command("get").action(todo("config get"));
83810
- config.command("set").action(todo("config set"));
83811
- config.command("validate").action(todo("config validate"));
83812
- config.command("path").action(todo("config path"));
83813
- const agent = program.command("agent").description("Manage local agent directories");
83814
- agent.command("list").action(todo("agent list"));
83815
- agent.command("add [id]").action(todo("agent add"));
83816
- agent.command("remove [id]").action(todo("agent remove"));
83817
- agent.command("enable [id]").action(todo("agent enable"));
83818
- agent.command("disable [id]").action(todo("agent disable"));
83997
+ program.command("agent").description("Inspect local agent directories").command("list").description("List agents discovered under --agents-dir (id, status, runtime, name, dir)").action(async () => {
83998
+ const opts = program.opts();
83999
+ try {
84000
+ const { runAgentList } = await import("./agent-list-CZbaXHPF.js");
84001
+ await runAgentList({
84002
+ agentsDir: opts.agentsDir,
84003
+ configPath: opts.config,
84004
+ root: opts.root
84005
+ });
84006
+ } catch (err) {
84007
+ console.error(`agentconnect agent list: ${err.message}`);
84008
+ process.exit(1);
84009
+ }
84010
+ });
83819
84011
  program.command("chat [message]").description("Discover an agent under --agents-dir (or --agent <name>) and chat over ACP").action(async (message) => {
83820
84012
  const opts = program.opts();
83821
84013
  try {
@@ -83834,6 +84026,6 @@ program.command("chat [message]").description("Discover an agent under --agents-
83834
84026
  });
83835
84027
  program.parse();
83836
84028
  //#endregion
83837
- export { __commonJSMin as C, safeParse$1 as S, record as _, _null as a, unknown as b, custom as c, literal as d, looseObject as f, preprocess as g, optional as h, _enum as i, discriminatedUnion as l, object as m, encodeFrame as n, array as o, number as p, DAEMON_VERSION as r, boolean as s, decodeFrames as t, intersection as u, string as v, __toESM as w, datetime as x, union as y };
84029
+ export { datetime as C, __toESM as E, unknown as S, __commonJSMin as T, optional as _, loadConfig as a, string as b, array as c, discriminatedUnion as d, intersection as f, object as g, number as h, discoverAgents as i, boolean as l, looseObject as m, encodeFrame as n, _enum as o, literal as p, DAEMON_VERSION as r, _null as s, decodeFrames as t, custom as u, preprocess as v, safeParse$1 as w, union as x, record as y };
83838
84030
 
83839
84031
  //# sourceMappingURL=index.js.map