@agentconnect.md/daemon 1.0.0-rc.24 → 1.0.0-rc.25

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
@@ -7567,6 +7567,9 @@ const AgentSchema = object({
7567
7567
  "paused"
7568
7568
  ]).default("active"),
7569
7569
  runtime: string(),
7570
+ description: string().optional(),
7571
+ reasoningEffort: string().optional(),
7572
+ executionMode: string().optional(),
7570
7573
  runtimeOverrides: object({
7571
7574
  model: string().optional(),
7572
7575
  env: array(object({
@@ -22985,6 +22988,44 @@ function watch$1(paths, options = {}) {
22985
22988
  return watcher;
22986
22989
  }
22987
22990
  //#endregion
22991
+ //#region src/agents/cp-overlay.ts
22992
+ /** Apply a CP `AgentSpec` onto a local agent, returning a new overlaid agent. */
22993
+ function overlayCpSpec(base, spec) {
22994
+ const envMap = new Map((base.runtimeOverrides?.env ?? []).map((e) => [e.name, e.value]));
22995
+ for (const [name, value] of Object.entries(spec.env ?? {})) envMap.set(name, value);
22996
+ const env = [...envMap].map(([name, value]) => ({
22997
+ name,
22998
+ value
22999
+ }));
23000
+ return {
23001
+ ...base,
23002
+ name: spec.name,
23003
+ ...spec.description !== void 0 ? { description: spec.description } : {},
23004
+ ...spec.reasoningEffort !== void 0 ? { reasoningEffort: spec.reasoningEffort } : {},
23005
+ ...spec.executionMode !== void 0 ? { executionMode: spec.executionMode } : {},
23006
+ runtimeOverrides: {
23007
+ ...base.runtimeOverrides,
23008
+ ...spec.model !== void 0 ? { model: spec.model } : {},
23009
+ env
23010
+ }
23011
+ };
23012
+ }
23013
+ /**
23014
+ * Runtime config the daemon can only surface to an ACP child as environment
23015
+ * variables: ACP `session/new`/`initialize` carry no model or system-prompt
23016
+ * field (SDK v1), so model / reasoning / prompt are exposed under `AGENTCONNECT_*`
23017
+ * for runtimes that read them. (`runtimeOverrides.env` is applied separately by
23018
+ * `agentChildEnv`.) Only emits a key when the value is set.
23019
+ */
23020
+ function cpRuntimeEnv(agent) {
23021
+ const out = {};
23022
+ if (agent.runtimeOverrides?.model) out.AGENTCONNECT_MODEL = agent.runtimeOverrides.model;
23023
+ if (agent.reasoningEffort) out.AGENTCONNECT_REASONING_EFFORT = agent.reasoningEffort;
23024
+ if (agent.executionMode) out.AGENTCONNECT_EXECUTION_MODE = agent.executionMode;
23025
+ if (agent.description) out.AGENTCONNECT_SYSTEM_PROMPT = agent.description;
23026
+ return out;
23027
+ }
23028
+ //#endregion
22988
23029
  //#region src/reconciler/reconciler.ts
22989
23030
  /** Stable identity-free signature of an agent's effective config. Excludes the
22990
23031
  * loader-only `dir`/`env` fields (present on `LoadedAgent`) so the comparison is
@@ -23042,6 +23083,10 @@ var LocalStore = class {
23042
23083
  id INTEGER PRIMARY KEY CHECK (id = 1),
23043
23084
  routingEpoch INTEGER, assignments TEXT, globalRules TEXT
23044
23085
  );
23086
+ CREATE TABLE IF NOT EXISTS cp_agents (
23087
+ id INTEGER PRIMARY KEY CHECK (id = 1),
23088
+ specs TEXT
23089
+ );
23045
23090
  `);
23046
23091
  }
23047
23092
  getSession(key) {
@@ -23075,6 +23120,13 @@ var LocalStore = class {
23075
23120
  globalRules
23076
23121
  });
23077
23122
  }
23123
+ getCpAgents() {
23124
+ return this.db.prepare("SELECT specs FROM cp_agents WHERE id = 1").get();
23125
+ }
23126
+ setCpAgents(specs) {
23127
+ this.db.prepare(`INSERT INTO cp_agents (id, specs) VALUES (1, @specs)
23128
+ ON CONFLICT(id) DO UPDATE SET specs=excluded.specs`).run({ specs });
23129
+ }
23078
23130
  close() {
23079
23131
  this.db.close();
23080
23132
  }
@@ -80533,6 +80585,14 @@ var CpClient = class {
80533
80585
  case "route/update":
80534
80586
  this.deps.configApply.applyRouteUpdate(frame.payload);
80535
80587
  return;
80588
+ case "agent/upsert": {
80589
+ const u = frame.payload;
80590
+ this.deps.configApply.applyAgentUpsert(u.agentId, u.spec);
80591
+ return;
80592
+ }
80593
+ case "agent/remove":
80594
+ this.deps.configApply.applyAgentRemove(frame.payload.agentId);
80595
+ return;
80536
80596
  case "agent/launch":
80537
80597
  case "agent/stop":
80538
80598
  case "agent/prompt":
@@ -80586,6 +80646,46 @@ var CpCronRegistry = class {
80586
80646
  }
80587
80647
  };
80588
80648
  //#endregion
80649
+ //#region src/cp/cp-agent-registry.ts
80650
+ var CpAgentRegistry = class {
80651
+ io;
80652
+ onChange;
80653
+ specs = /* @__PURE__ */ new Map();
80654
+ constructor(io, onChange) {
80655
+ this.io = io;
80656
+ this.onChange = onChange;
80657
+ const s = io.load();
80658
+ if (s) this.specs = new Map(Object.entries(s));
80659
+ }
80660
+ /** Add or replace one agent's spec (agent/upsert EVT). */
80661
+ upsert(agentId, spec) {
80662
+ this.specs.set(agentId, spec);
80663
+ this.changed();
80664
+ }
80665
+ /** Drop one agent's spec (agent/remove EVT). No-op if absent. */
80666
+ remove(agentId) {
80667
+ if (this.specs.delete(agentId)) this.changed();
80668
+ }
80669
+ /** Make the live set exactly `roster` (register/ok reconcile snapshot). */
80670
+ converge(roster) {
80671
+ const next = /* @__PURE__ */ new Map();
80672
+ for (const { agentId, ...spec } of roster) next.set(agentId, spec);
80673
+ this.specs = next;
80674
+ this.changed();
80675
+ }
80676
+ get(agentId) {
80677
+ return this.specs.get(agentId);
80678
+ }
80679
+ /** agentIds the CP currently wants present. */
80680
+ ids() {
80681
+ return [...this.specs.keys()];
80682
+ }
80683
+ changed() {
80684
+ this.io.save(Object.fromEntries(this.specs));
80685
+ this.onChange();
80686
+ }
80687
+ };
80688
+ //#endregion
80589
80689
  //#region src/cp/config-apply.ts
80590
80690
  const LOG_LEVELS = /* @__PURE__ */ new Set([
80591
80691
  "trace",
@@ -80670,6 +80770,7 @@ var Daemon = class {
80670
80770
  opts;
80671
80771
  store;
80672
80772
  agents = /* @__PURE__ */ new Map();
80773
+ fileAgents = /* @__PURE__ */ new Map();
80673
80774
  hosts = /* @__PURE__ */ new Map();
80674
80775
  connections = [];
80675
80776
  scheduler;
@@ -80686,6 +80787,7 @@ var Daemon = class {
80686
80787
  runtimeNames = {};
80687
80788
  cpClient;
80688
80789
  cpCrons;
80790
+ cpAgents;
80689
80791
  botUserIds = {};
80690
80792
  cpRouting;
80691
80793
  constructor(opts = {}) {
@@ -80710,7 +80812,7 @@ var Daemon = class {
80710
80812
  this.log.info(`control plane: ${cfg.controlPlane?.enabled ? `enabled (${cfg.controlPlane.url ?? "no url"})` : "disabled — running local"}`);
80711
80813
  this.agentsDir = cfg.agentsDir;
80712
80814
  const agents = this.loadAgentList();
80713
- for (const a of agents) this.agents.set(a.id, a);
80815
+ this.fileAgents = new Map(agents.map((a) => [a.id, a]));
80714
80816
  this.log.info(`loaded ${agents.length} agent(s) from ${this.agentsDir}${agents.length ? `: ${agents.map((a) => a.id).join(", ")}` : ""}`);
80715
80817
  this.root = root;
80716
80818
  const resolvedRuntimes = await resolveRuntimes(cfg, root, {
@@ -80734,6 +80836,14 @@ var Daemon = class {
80734
80836
  },
80735
80837
  save: (s) => this.store.setCpRouting(s.routingEpoch, JSON.stringify(s.assignments), JSON.stringify(s.globalRules))
80736
80838
  });
80839
+ this.cpAgents = new CpAgentRegistry({
80840
+ load: () => {
80841
+ const row = this.store.getCpAgents();
80842
+ return row ? JSON.parse(row.specs) : void 0;
80843
+ },
80844
+ save: (s) => this.store.setCpAgents(JSON.stringify(s))
80845
+ }, () => void this.reconcile().catch((err) => this.log.error(`cp: agent reconcile failed: ${err.stack ?? err}`)));
80846
+ for (const a of this.effectiveAgents()) this.agents.set(a.id, a);
80737
80847
  this.sessions = new SessionManager({
80738
80848
  store: this.store,
80739
80849
  hostFor: (agentId) => this.ensureHostAsync(agentId),
@@ -80789,8 +80899,51 @@ var Daemon = class {
80789
80899
  loadAgentList() {
80790
80900
  return this.opts.agentName ? [selectAgent(this.agentsDir, this.opts.agentName)] : loadAgents(this.agentsDir);
80791
80901
  }
80902
+ /**
80903
+ * Effective agent set = on-disk `agent.json` base with the CP spec overlaid
80904
+ * (joined by id; CP owns name/description/model/reasoning/execution/env, the
80905
+ * file owns runtime/workspace). CP-only ids with no local base are skipped —
80906
+ * not runnable — and surfaced via cpDegradedScopes().
80907
+ */
80908
+ effectiveAgents() {
80909
+ return [...this.fileAgents.values()].map((base) => {
80910
+ const spec = this.cpAgents?.get(base.id);
80911
+ return spec ? overlayCpSpec(base, spec) : base;
80912
+ });
80913
+ }
80914
+ /**
80915
+ * Converge the running set to the desired effective agents. Driven by both the
80916
+ * `agents/**` file-watch AND CP convergence (agent/upsert, agent/remove, the
80917
+ * register/ok roster, via the registry's onChange). `diffAgents` detects a
80918
+ * changed effective config (runtime/workspace/model/prompt/env/…) and restarts
80919
+ * the host lazily so the next session picks up fresh config.
80920
+ *
80921
+ * Single-flight: overlapping triggers (e.g. a burst of CP frames, or a file
80922
+ * event landing mid-reconcile) coalesce into one trailing re-run so we never
80923
+ * run two passes concurrently and double-evict a host.
80924
+ */
80925
+ reconcileRun;
80926
+ reconcilePending = false;
80792
80927
  async reconcile() {
80793
- const desired = this.loadAgentList();
80928
+ if (this.reconcileRun) {
80929
+ this.reconcilePending = true;
80930
+ return this.reconcileRun;
80931
+ }
80932
+ this.reconcileRun = this.runReconcile();
80933
+ try {
80934
+ await this.reconcileRun;
80935
+ } finally {
80936
+ this.reconcileRun = void 0;
80937
+ }
80938
+ if (this.reconcilePending) {
80939
+ this.reconcilePending = false;
80940
+ await this.reconcile();
80941
+ }
80942
+ }
80943
+ async runReconcile() {
80944
+ const files = this.loadAgentList();
80945
+ this.fileAgents = new Map(files.map((a) => [a.id, a]));
80946
+ const desired = this.effectiveAgents();
80794
80947
  const { toStart, toStop, toRestart } = diffAgents(desired, this.agents);
80795
80948
  if (toStart.length || toStop.length || toRestart.length) this.log.info(`reconcile: ${desired.length} desired agent(s) from ${this.agentsDir}; start=[${toStart.map((a) => a.id).join(", ")}] stop=[${toStop.join(", ")}] restart=[${toRestart.map((a) => a.id).join(", ")}]`);
80796
80949
  else this.log.debug(`reconcile: no changes (${desired.length} desired agent(s))`);
@@ -80825,7 +80978,10 @@ var Daemon = class {
80825
80978
  if (!runtime) throw new Error(`runtime "${agent.runtime}" not available: not installed on this host, or absent from config.runtimes / the ACP registry`);
80826
80979
  host = new AcpHost(runtime, {
80827
80980
  onUpdate,
80828
- env: agentChildEnv(agent),
80981
+ env: {
80982
+ ...agentChildEnv(agent),
80983
+ ...cpRuntimeEnv(agent)
80984
+ },
80829
80985
  log: this.log
80830
80986
  });
80831
80987
  }
@@ -80929,10 +81085,15 @@ var Daemon = class {
80929
81085
  resolveCpAgent(agentId) {
80930
81086
  return resolveAgentIntegration(this.agents.get(agentId), this.botUserIds);
80931
81087
  }
80932
- /** agentIds of CP rules that currently resolve to null (no servable Slack integration). */
81088
+ /**
81089
+ * agentIds the daemon can't fully serve: CP routing rules with no servable
81090
+ * Slack integration, plus CP agent specs with no on-disk base (no runtime /
81091
+ * workspace to materialize — the spec alone can't be run).
81092
+ */
80933
81093
  cpDegradedScopes() {
80934
81094
  const out = /* @__PURE__ */ new Set();
80935
81095
  for (const cpRule of this.cpRouting?.effectiveRules() ?? []) if (!this.resolveCpAgent(cpRule.agentId)) out.add(cpRule.agentId);
81096
+ for (const id of this.cpAgents?.ids() ?? []) if (!this.fileAgents.has(id)) out.add(id);
80936
81097
  return [...out];
80937
81098
  }
80938
81099
  async dispatch(agentId, msg, integrationId) {
@@ -80996,6 +81157,7 @@ var Daemon = class {
80996
81157
  },
80997
81158
  applyReconcileSnapshot: (snap) => {
80998
81159
  this.cpCrons?.converge(snap.crons);
81160
+ this.cpAgents?.converge(snap.agents);
80999
81161
  this.cpRouting?.converge({
81000
81162
  routingEpoch: snap.routingEpoch,
81001
81163
  assignments: snap.assignments,
@@ -81003,7 +81165,10 @@ var Daemon = class {
81003
81165
  });
81004
81166
  if (snap.leases.length) this.log.debug(`cp: ${snap.leases.length} lease(s) noted (secrets handled later)`);
81005
81167
  if (snap.assignments.length) this.log.debug(`cp: converged ${snap.assignments.length} assignment(s)`);
81168
+ if (snap.agents.length) this.log.debug(`cp: converged ${snap.agents.length} agent spec(s)`);
81006
81169
  },
81170
+ applyAgentUpsert: (agentId, spec) => this.cpAgents?.upsert(agentId, spec),
81171
+ applyAgentRemove: (agentId) => this.cpAgents?.remove(agentId),
81007
81172
  upsertCron: (cron) => this.cpCrons.upsert(cron),
81008
81173
  removeCron: (cronId) => this.cpCrons.remove(cronId),
81009
81174
  applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),