@agentconnect.md/daemon 1.0.0-rc.27 → 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 +159 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17215,6 +17215,7 @@ const AgentWorkspace = discriminatedUnion("mode", [object({ mode: literal("scrat
|
|
|
17215
17215
|
const AgentSpec = object({
|
|
17216
17216
|
name: string(),
|
|
17217
17217
|
description: string().optional(),
|
|
17218
|
+
runtime: string().optional(),
|
|
17218
17219
|
model: string().optional(),
|
|
17219
17220
|
reasoningEffort: string().optional(),
|
|
17220
17221
|
executionMode: string().optional(),
|
|
@@ -23094,27 +23095,6 @@ function watch$1(paths, options = {}) {
|
|
|
23094
23095
|
}
|
|
23095
23096
|
//#endregion
|
|
23096
23097
|
//#region src/agents/cp-overlay.ts
|
|
23097
|
-
/** Apply a CP `AgentSpec` onto a local agent, returning a new overlaid agent. */
|
|
23098
|
-
function overlayCpSpec(base, spec) {
|
|
23099
|
-
const envMap = new Map((base.runtimeOverrides?.env ?? []).map((e) => [e.name, e.value]));
|
|
23100
|
-
for (const [name, value] of Object.entries(spec.env ?? {})) envMap.set(name, value);
|
|
23101
|
-
const env = [...envMap].map(([name, value]) => ({
|
|
23102
|
-
name,
|
|
23103
|
-
value
|
|
23104
|
-
}));
|
|
23105
|
-
return {
|
|
23106
|
-
...base,
|
|
23107
|
-
name: spec.name,
|
|
23108
|
-
...spec.description !== void 0 ? { description: spec.description } : {},
|
|
23109
|
-
...spec.reasoningEffort !== void 0 ? { reasoningEffort: spec.reasoningEffort } : {},
|
|
23110
|
-
...spec.executionMode !== void 0 ? { executionMode: spec.executionMode } : {},
|
|
23111
|
-
runtimeOverrides: {
|
|
23112
|
-
...base.runtimeOverrides,
|
|
23113
|
-
...spec.model !== void 0 ? { model: spec.model } : {},
|
|
23114
|
-
env
|
|
23115
|
-
}
|
|
23116
|
-
};
|
|
23117
|
-
}
|
|
23118
23098
|
/**
|
|
23119
23099
|
* Runtime config the daemon can only surface to an ACP child as environment
|
|
23120
23100
|
* variables: ACP `session/new`/`initialize` carry no model or system-prompt
|
|
@@ -23188,10 +23168,6 @@ var LocalStore = class {
|
|
|
23188
23168
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
23189
23169
|
routingEpoch INTEGER, assignments TEXT, globalRules TEXT
|
|
23190
23170
|
);
|
|
23191
|
-
CREATE TABLE IF NOT EXISTS cp_agents (
|
|
23192
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
23193
|
-
specs TEXT
|
|
23194
|
-
);
|
|
23195
23171
|
`);
|
|
23196
23172
|
}
|
|
23197
23173
|
getSession(key) {
|
|
@@ -23225,13 +23201,6 @@ var LocalStore = class {
|
|
|
23225
23201
|
globalRules
|
|
23226
23202
|
});
|
|
23227
23203
|
}
|
|
23228
|
-
getCpAgents() {
|
|
23229
|
-
return this.db.prepare("SELECT specs FROM cp_agents WHERE id = 1").get();
|
|
23230
|
-
}
|
|
23231
|
-
setCpAgents(specs) {
|
|
23232
|
-
this.db.prepare(`INSERT INTO cp_agents (id, specs) VALUES (1, @specs)
|
|
23233
|
-
ON CONFLICT(id) DO UPDATE SET specs=excluded.specs`).run({ specs });
|
|
23234
|
-
}
|
|
23235
23204
|
close() {
|
|
23236
23205
|
this.db.close();
|
|
23237
23206
|
}
|
|
@@ -81148,42 +81117,164 @@ var CpCronRegistry = class {
|
|
|
81148
81117
|
}
|
|
81149
81118
|
};
|
|
81150
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
|
|
81151
81251
|
//#region src/cp/cp-agent-registry.ts
|
|
81152
81252
|
var CpAgentRegistry = class {
|
|
81153
|
-
|
|
81253
|
+
agentsDir;
|
|
81254
|
+
deps;
|
|
81154
81255
|
onChange;
|
|
81155
|
-
|
|
81156
|
-
|
|
81157
|
-
this.
|
|
81256
|
+
constructor(agentsDir, deps, onChange) {
|
|
81257
|
+
this.agentsDir = agentsDir;
|
|
81258
|
+
this.deps = deps;
|
|
81158
81259
|
this.onChange = onChange;
|
|
81159
|
-
const s = io.load();
|
|
81160
|
-
if (s) this.specs = new Map(Object.entries(s));
|
|
81161
81260
|
}
|
|
81162
|
-
/** Add or
|
|
81261
|
+
/** Add or merge one agent's spec onto disk (agent/upsert EVT). */
|
|
81163
81262
|
upsert(agentId, spec) {
|
|
81164
|
-
this.
|
|
81165
|
-
this.
|
|
81263
|
+
writeAgentSpec(this.agentsDir, agentId, spec, this.deps);
|
|
81264
|
+
this.onChange();
|
|
81166
81265
|
}
|
|
81167
|
-
/**
|
|
81266
|
+
/** Delete one agent's on-disk dir (agent/remove EVT). Unconditional. */
|
|
81168
81267
|
remove(agentId) {
|
|
81169
|
-
|
|
81268
|
+
removeAgent(this.agentsDir, agentId);
|
|
81269
|
+
this.onChange();
|
|
81170
81270
|
}
|
|
81171
|
-
/**
|
|
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
|
+
*/
|
|
81172
81276
|
converge(roster) {
|
|
81173
|
-
const
|
|
81174
|
-
for (const { agentId, ...spec } of roster) next.set(agentId, spec);
|
|
81175
|
-
this.specs = next;
|
|
81176
|
-
this.changed();
|
|
81177
|
-
}
|
|
81178
|
-
get(agentId) {
|
|
81179
|
-
return this.specs.get(agentId);
|
|
81180
|
-
}
|
|
81181
|
-
/** agentIds the CP currently wants present. */
|
|
81182
|
-
ids() {
|
|
81183
|
-
return [...this.specs.keys()];
|
|
81184
|
-
}
|
|
81185
|
-
changed() {
|
|
81186
|
-
this.io.save(Object.fromEntries(this.specs));
|
|
81277
|
+
for (const { agentId, ...spec } of roster) writeAgentSpec(this.agentsDir, agentId, spec, this.deps);
|
|
81187
81278
|
this.onChange();
|
|
81188
81279
|
}
|
|
81189
81280
|
};
|
|
@@ -81339,12 +81430,9 @@ var Daemon = class {
|
|
|
81339
81430
|
},
|
|
81340
81431
|
save: (s) => this.store.setCpRouting(s.routingEpoch, JSON.stringify(s.assignments), JSON.stringify(s.globalRules))
|
|
81341
81432
|
});
|
|
81342
|
-
this.cpAgents = new CpAgentRegistry({
|
|
81343
|
-
|
|
81344
|
-
|
|
81345
|
-
return row ? JSON.parse(row.specs) : void 0;
|
|
81346
|
-
},
|
|
81347
|
-
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)
|
|
81348
81436
|
}, () => void this.reconcile().catch((err) => this.log.error(`cp: agent reconcile failed: ${err.stack ?? err}`)));
|
|
81349
81437
|
for (const a of this.effectiveAgents()) this.agents.set(a.id, a);
|
|
81350
81438
|
this.mcp = new McpControlServer({
|
|
@@ -81435,16 +81523,13 @@ var Daemon = class {
|
|
|
81435
81523
|
return this.opts.agentName ? [selectAgent(this.agentsDir, this.opts.agentName)] : loadAgents(this.agentsDir);
|
|
81436
81524
|
}
|
|
81437
81525
|
/**
|
|
81438
|
-
* Effective agent set = on-disk `agent.json`
|
|
81439
|
-
*
|
|
81440
|
-
*
|
|
81441
|
-
*
|
|
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.
|
|
81442
81530
|
*/
|
|
81443
81531
|
effectiveAgents() {
|
|
81444
|
-
return [...this.fileAgents.values()]
|
|
81445
|
-
const spec = this.cpAgents?.get(base.id);
|
|
81446
|
-
return spec ? overlayCpSpec(base, spec) : base;
|
|
81447
|
-
});
|
|
81532
|
+
return [...this.fileAgents.values()];
|
|
81448
81533
|
}
|
|
81449
81534
|
/**
|
|
81450
81535
|
* Converge the running set to the desired effective agents. Driven by both the
|
|
@@ -81622,13 +81707,12 @@ var Daemon = class {
|
|
|
81622
81707
|
}
|
|
81623
81708
|
/**
|
|
81624
81709
|
* agentIds the daemon can't fully serve: CP routing rules with no servable
|
|
81625
|
-
* Slack integration
|
|
81626
|
-
*
|
|
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.)
|
|
81627
81712
|
*/
|
|
81628
81713
|
cpDegradedScopes() {
|
|
81629
81714
|
const out = /* @__PURE__ */ new Set();
|
|
81630
81715
|
for (const cpRule of this.cpRouting?.effectiveRules() ?? []) if (!this.resolveCpAgent(cpRule.agentId)) out.add(cpRule.agentId);
|
|
81631
|
-
for (const id of this.cpAgents?.ids() ?? []) if (!this.fileAgents.has(id)) out.add(id);
|
|
81632
81716
|
return [...out];
|
|
81633
81717
|
}
|
|
81634
81718
|
async dispatch(agentId, msg, integrationId) {
|