@agentconnect.md/daemon 1.0.0-rc.28 → 1.0.0-rc.29

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
@@ -23095,27 +23095,6 @@ function watch$1(paths, options = {}) {
23095
23095
  }
23096
23096
  //#endregion
23097
23097
  //#region src/agents/cp-overlay.ts
23098
- /** Apply a CP `AgentSpec` onto a local agent, returning a new overlaid agent. */
23099
- function overlayCpSpec(base, spec) {
23100
- const envMap = new Map((base.runtimeOverrides?.env ?? []).map((e) => [e.name, e.value]));
23101
- for (const [name, value] of Object.entries(spec.env ?? {})) envMap.set(name, value);
23102
- const env = [...envMap].map(([name, value]) => ({
23103
- name,
23104
- value
23105
- }));
23106
- return {
23107
- ...base,
23108
- name: spec.name,
23109
- ...spec.description !== void 0 ? { description: spec.description } : {},
23110
- ...spec.reasoningEffort !== void 0 ? { reasoningEffort: spec.reasoningEffort } : {},
23111
- ...spec.executionMode !== void 0 ? { executionMode: spec.executionMode } : {},
23112
- runtimeOverrides: {
23113
- ...base.runtimeOverrides,
23114
- ...spec.model !== void 0 ? { model: spec.model } : {},
23115
- env
23116
- }
23117
- };
23118
- }
23119
23098
  /**
23120
23099
  * Runtime config the daemon can only surface to an ACP child as environment
23121
23100
  * variables: ACP `session/new`/`initialize` carry no model or system-prompt
@@ -23189,10 +23168,6 @@ var LocalStore = class {
23189
23168
  id INTEGER PRIMARY KEY CHECK (id = 1),
23190
23169
  routingEpoch INTEGER, assignments TEXT, globalRules TEXT
23191
23170
  );
23192
- CREATE TABLE IF NOT EXISTS cp_agents (
23193
- id INTEGER PRIMARY KEY CHECK (id = 1),
23194
- specs TEXT
23195
- );
23196
23171
  `);
23197
23172
  }
23198
23173
  getSession(key) {
@@ -23226,13 +23201,6 @@ var LocalStore = class {
23226
23201
  globalRules
23227
23202
  });
23228
23203
  }
23229
- getCpAgents() {
23230
- return this.db.prepare("SELECT specs FROM cp_agents WHERE id = 1").get();
23231
- }
23232
- setCpAgents(specs) {
23233
- this.db.prepare(`INSERT INTO cp_agents (id, specs) VALUES (1, @specs)
23234
- ON CONFLICT(id) DO UPDATE SET specs=excluded.specs`).run({ specs });
23235
- }
23236
23204
  close() {
23237
23205
  this.db.close();
23238
23206
  }
@@ -81149,42 +81117,164 @@ var CpCronRegistry = class {
81149
81117
  }
81150
81118
  };
81151
81119
  //#endregion
81120
+ //#region src/agents/write-agent.ts
81121
+ /**
81122
+ * Persist CP-owned agent spec onto the daemon's on-disk `agent.json`, which is
81123
+ * the SINGLE SOURCE OF TRUTH for agent config. The CP pushes an `AgentSpec`
81124
+ * (agent/upsert + the register/ok roster); we map its CP-owned fields onto the
81125
+ * matching `<agentsDir>/<id>/agent.json`:
81126
+ *
81127
+ * - if one with that id exists → field-level MERGE the CP-owned keys into it;
81128
+ * - if none exists → CREATE a new `<agentsDir>/<id>/agent.json`.
81129
+ *
81130
+ * CRITICAL: we operate on the RAW JSON TEXT (readFileSync → JSON.parse → set
81131
+ * only CP-owned keys → writeFileSync). We never serialize the parsed/interpolated
81132
+ * `LoadedAgent` — that has `${VAR}` already substituted (secrets baked in) and
81133
+ * `workspace.path` rewritten to an absolute path. Editing the raw file preserves
81134
+ * `${VAR}` templates and the original relative `workspace.path`.
81135
+ *
81136
+ * Locally-owned keys (id, status, runtime, integrations, output, permissions,
81137
+ * crons, workspace.path / pullOnNewSession / skills) are preserved untouched.
81138
+ */
81139
+ /** Map a CP workspace mode onto the daemon's AgentSchema workspace mode. */
81140
+ function mapWorkspaceMode(mode) {
81141
+ return mode === "scratch" ? "from-scratch" : "git-repo";
81142
+ }
81143
+ /**
81144
+ * Locate the on-disk `agent.json` whose INTERNAL `id` equals `agentId`.
81145
+ * Agents live under arbitrary directory layouts (discoverAgents walks recursively
81146
+ * and the dir name need NOT equal the id), so we must match by parsed id — never
81147
+ * assume the file sits at `<agentsDir>/<id>/agent.json`. Returns the file path, or
81148
+ * undefined if no agent with that id exists. Unparseable files are skipped.
81149
+ */
81150
+ function findAgentFileById(agentsDir, agentId) {
81151
+ for (const file of findAgentFiles(agentsDir)) try {
81152
+ if (JSON.parse(readFileSync(file, "utf8")).id === agentId) return file;
81153
+ } catch {}
81154
+ }
81155
+ /**
81156
+ * Apply the CP-owned spec fields onto a parsed raw agent.json object IN PLACE.
81157
+ * `creating` controls whether workspace.path may be set (only on create — an
81158
+ * update never overwrites a path).
81159
+ */
81160
+ function applySpecFields(raw, spec, opts) {
81161
+ raw.name = spec.name;
81162
+ if (spec.description !== void 0) raw.description = spec.description;
81163
+ if (spec.reasoningEffort !== void 0) raw.reasoningEffort = spec.reasoningEffort;
81164
+ if (spec.executionMode !== void 0) raw.executionMode = spec.executionMode;
81165
+ if (spec.model !== void 0 || spec.env !== void 0) {
81166
+ const ro = typeof raw.runtimeOverrides === "object" && raw.runtimeOverrides !== null ? raw.runtimeOverrides : {};
81167
+ if (spec.model !== void 0) ro.model = spec.model;
81168
+ if (spec.env !== void 0) ro.env = Object.entries(spec.env).map(([name, value]) => ({
81169
+ name,
81170
+ value
81171
+ }));
81172
+ raw.runtimeOverrides = ro;
81173
+ }
81174
+ if (spec.workspace !== void 0) {
81175
+ const ws = spec.workspace;
81176
+ const existing = typeof raw.workspace === "object" && raw.workspace !== null ? raw.workspace : {};
81177
+ existing.mode = mapWorkspaceMode(ws.mode);
81178
+ if (ws.mode === "github") {
81179
+ existing.gitRepo = ws.gitRepo;
81180
+ existing.gitBranch = ws.branch;
81181
+ }
81182
+ if (opts.creating && existing.path === void 0) existing.path = join(opts.agentDir, "workspace");
81183
+ raw.workspace = existing;
81184
+ }
81185
+ }
81186
+ /**
81187
+ * Create-or-merge the CP spec onto `<agentsDir>/<agentId>/agent.json`.
81188
+ *
81189
+ * MERGE (file exists): re-read the raw JSON text, set only CP-owned keys,
81190
+ * write it back pretty-printed. Preserves ${VAR} templates, the relative
81191
+ * workspace.path, and all locally-owned keys.
81192
+ *
81193
+ * CREATE (no file): synthesize a minimal valid agent.json under a fresh
81194
+ * `<agentsDir>/<agentId>/` dir — runtime from `spec.runtime` (falling back to
81195
+ * the first known runtime + a warn), workspace translated or defaulted to
81196
+ * from-scratch, path daemon-generated.
81197
+ */
81198
+ function writeAgentSpec(agentsDir, agentId, spec, deps) {
81199
+ const existingFile = findAgentFileById(agentsDir, agentId);
81200
+ if (existingFile) {
81201
+ const raw = JSON.parse(readFileSync(existingFile, "utf8"));
81202
+ applySpecFields(raw, spec, {
81203
+ agentId,
81204
+ agentDir: dirname(existingFile),
81205
+ creating: false
81206
+ });
81207
+ writeFileSync(existingFile, JSON.stringify(raw, null, 2) + "\n");
81208
+ return;
81209
+ }
81210
+ const agentDir = join(agentsDir, agentId);
81211
+ const file = join(agentDir, "agent.json");
81212
+ let runtime = spec.runtime;
81213
+ if (!runtime) {
81214
+ runtime = deps.knownRuntimes[0];
81215
+ if (!runtime) throw new Error(`cannot create agent "${agentId}": spec has no runtime and no runtimes are known`);
81216
+ deps.warn?.(`cp: agent "${agentId}" spec has no runtime; defaulting to "${runtime}"`);
81217
+ }
81218
+ const raw = {
81219
+ id: agentId,
81220
+ name: spec.name,
81221
+ status: "active",
81222
+ runtime,
81223
+ workspace: {
81224
+ mode: "from-scratch",
81225
+ path: join(agentDir, "workspace")
81226
+ }
81227
+ };
81228
+ applySpecFields(raw, spec, {
81229
+ agentId,
81230
+ agentDir,
81231
+ creating: true
81232
+ });
81233
+ mkdirSync(agentDir, { recursive: true });
81234
+ writeFileSync(file, JSON.stringify(raw, null, 2) + "\n");
81235
+ }
81236
+ /**
81237
+ * Delete the agent whose internal `id` is `agentId`. Called ONLY from the
81238
+ * `agent/remove` handler — unconditional. Locates the file by id (any directory
81239
+ * layout) and removes its containing dir; falls back to `<agentsDir>/<id>` when
81240
+ * no such agent exists (no-op). `discoverAgents` treats an agent.json dir as a
81241
+ * leaf, so the containing dir is the agent's own dir.
81242
+ */
81243
+ function removeAgent(agentsDir, agentId) {
81244
+ const existingFile = findAgentFileById(agentsDir, agentId);
81245
+ rmSync(existingFile ? dirname(existingFile) : join(agentsDir, agentId), {
81246
+ recursive: true,
81247
+ force: true
81248
+ });
81249
+ }
81250
+ //#endregion
81152
81251
  //#region src/cp/cp-agent-registry.ts
81153
81252
  var CpAgentRegistry = class {
81154
- io;
81253
+ agentsDir;
81254
+ deps;
81155
81255
  onChange;
81156
- specs = /* @__PURE__ */ new Map();
81157
- constructor(io, onChange) {
81158
- this.io = io;
81256
+ constructor(agentsDir, deps, onChange) {
81257
+ this.agentsDir = agentsDir;
81258
+ this.deps = deps;
81159
81259
  this.onChange = onChange;
81160
- const s = io.load();
81161
- if (s) this.specs = new Map(Object.entries(s));
81162
81260
  }
81163
- /** Add or replace one agent's spec (agent/upsert EVT). */
81261
+ /** Add or merge one agent's spec onto disk (agent/upsert EVT). */
81164
81262
  upsert(agentId, spec) {
81165
- this.specs.set(agentId, spec);
81166
- this.changed();
81263
+ writeAgentSpec(this.agentsDir, agentId, spec, this.deps);
81264
+ this.onChange();
81167
81265
  }
81168
- /** Drop one agent's spec (agent/remove EVT). No-op if absent. */
81266
+ /** Delete one agent's on-disk dir (agent/remove EVT). Unconditional. */
81169
81267
  remove(agentId) {
81170
- if (this.specs.delete(agentId)) this.changed();
81268
+ removeAgent(this.agentsDir, agentId);
81269
+ this.onChange();
81171
81270
  }
81172
- /** Make the live set exactly `roster` (register/ok reconcile snapshot). */
81271
+ /**
81272
+ * Apply the register/ok reconcile roster: create-or-merge EACH entry and
81273
+ * NOTHING else. Never prunes agents absent from the roster (deletion is only
81274
+ * via agent/remove).
81275
+ */
81173
81276
  converge(roster) {
81174
- const next = /* @__PURE__ */ new Map();
81175
- for (const { agentId, ...spec } of roster) next.set(agentId, spec);
81176
- this.specs = next;
81177
- this.changed();
81178
- }
81179
- get(agentId) {
81180
- return this.specs.get(agentId);
81181
- }
81182
- /** agentIds the CP currently wants present. */
81183
- ids() {
81184
- return [...this.specs.keys()];
81185
- }
81186
- changed() {
81187
- this.io.save(Object.fromEntries(this.specs));
81277
+ for (const { agentId, ...spec } of roster) writeAgentSpec(this.agentsDir, agentId, spec, this.deps);
81188
81278
  this.onChange();
81189
81279
  }
81190
81280
  };
@@ -81340,12 +81430,9 @@ var Daemon = class {
81340
81430
  },
81341
81431
  save: (s) => this.store.setCpRouting(s.routingEpoch, JSON.stringify(s.assignments), JSON.stringify(s.globalRules))
81342
81432
  });
81343
- this.cpAgents = new CpAgentRegistry({
81344
- load: () => {
81345
- const row = this.store.getCpAgents();
81346
- return row ? JSON.parse(row.specs) : void 0;
81347
- },
81348
- save: (s) => this.store.setCpAgents(JSON.stringify(s))
81433
+ this.cpAgents = new CpAgentRegistry(this.agentsDir, {
81434
+ knownRuntimes: Object.keys(this.runtimes),
81435
+ warn: (m) => this.log.warn(m)
81349
81436
  }, () => void this.reconcile().catch((err) => this.log.error(`cp: agent reconcile failed: ${err.stack ?? err}`)));
81350
81437
  for (const a of this.effectiveAgents()) this.agents.set(a.id, a);
81351
81438
  this.mcp = new McpControlServer({
@@ -81436,16 +81523,13 @@ var Daemon = class {
81436
81523
  return this.opts.agentName ? [selectAgent(this.agentsDir, this.opts.agentName)] : loadAgents(this.agentsDir);
81437
81524
  }
81438
81525
  /**
81439
- * Effective agent set = on-disk `agent.json` base with the CP spec overlaid
81440
- * (joined by id; CP owns name/description/model/reasoning/execution/env, the
81441
- * file owns runtime/workspace). CP-only ids with no local base are skipped —
81442
- * not runnable and surfaced via cpDegradedScopes().
81526
+ * Effective agent set = the on-disk `agent.json` files verbatim. The CP spec is
81527
+ * no longer overlaid in memory — `agent/upsert` writes it straight to disk (see
81528
+ * cp/cp-agent-registry.ts agents/write-agent.ts), so the loaded file already
81529
+ * carries the CP-owned fields.
81443
81530
  */
81444
81531
  effectiveAgents() {
81445
- return [...this.fileAgents.values()].map((base) => {
81446
- const spec = this.cpAgents?.get(base.id);
81447
- return spec ? overlayCpSpec(base, spec) : base;
81448
- });
81532
+ return [...this.fileAgents.values()];
81449
81533
  }
81450
81534
  /**
81451
81535
  * Converge the running set to the desired effective agents. Driven by both the
@@ -81623,13 +81707,12 @@ var Daemon = class {
81623
81707
  }
81624
81708
  /**
81625
81709
  * agentIds the daemon can't fully serve: CP routing rules with no servable
81626
- * Slack integration, plus CP agent specs with no on-disk base (no runtime /
81627
- * workspace to materialize the spec alone can't be run).
81710
+ * Slack integration. (CP agent specs are now written to disk and create a
81711
+ * runnable agent.json, so they no longer contribute a "no base" degraded scope.)
81628
81712
  */
81629
81713
  cpDegradedScopes() {
81630
81714
  const out = /* @__PURE__ */ new Set();
81631
81715
  for (const cpRule of this.cpRouting?.effectiveRules() ?? []) if (!this.resolveCpAgent(cpRule.agentId)) out.add(cpRule.agentId);
81632
- for (const id of this.cpAgents?.ids() ?? []) if (!this.fileAgents.has(id)) out.add(id);
81633
81716
  return [...out];
81634
81717
  }
81635
81718
  async dispatch(agentId, msg, integrationId) {