@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 +158 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
81253
|
+
agentsDir;
|
|
81254
|
+
deps;
|
|
81155
81255
|
onChange;
|
|
81156
|
-
|
|
81157
|
-
|
|
81158
|
-
this.
|
|
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
|
|
81261
|
+
/** Add or merge one agent's spec onto disk (agent/upsert EVT). */
|
|
81164
81262
|
upsert(agentId, spec) {
|
|
81165
|
-
this.
|
|
81166
|
-
this.
|
|
81263
|
+
writeAgentSpec(this.agentsDir, agentId, spec, this.deps);
|
|
81264
|
+
this.onChange();
|
|
81167
81265
|
}
|
|
81168
|
-
/**
|
|
81266
|
+
/** Delete one agent's on-disk dir (agent/remove EVT). Unconditional. */
|
|
81169
81267
|
remove(agentId) {
|
|
81170
|
-
|
|
81268
|
+
removeAgent(this.agentsDir, agentId);
|
|
81269
|
+
this.onChange();
|
|
81171
81270
|
}
|
|
81172
|
-
/**
|
|
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
|
|
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
|
-
|
|
81345
|
-
|
|
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`
|
|
81440
|
-
*
|
|
81441
|
-
*
|
|
81442
|
-
*
|
|
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()]
|
|
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
|
|
81627
|
-
*
|
|
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) {
|