@agentconnect.md/daemon 1.0.0-rc.23 → 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 +288 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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({
|
|
@@ -16741,6 +16744,9 @@ var AcpHost = class {
|
|
|
16741
16744
|
opts;
|
|
16742
16745
|
child;
|
|
16743
16746
|
conn;
|
|
16747
|
+
live = /* @__PURE__ */ new Set();
|
|
16748
|
+
loadingSessions = /* @__PURE__ */ new Set();
|
|
16749
|
+
canLoad = false;
|
|
16744
16750
|
constructor(runtime, opts) {
|
|
16745
16751
|
this.runtime = runtime;
|
|
16746
16752
|
this.opts = opts;
|
|
@@ -16764,6 +16770,7 @@ var AcpHost = class {
|
|
|
16764
16770
|
const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
16765
16771
|
const self = this;
|
|
16766
16772
|
this.conn = client({ name: "agentconnect" }).onNotification(methods.client.session.update, (ctx) => {
|
|
16773
|
+
if (self.loadingSessions.has(ctx.params.sessionId)) return;
|
|
16767
16774
|
self.opts.onUpdate(ctx.params.sessionId, ctx.params.update);
|
|
16768
16775
|
}).onRequest(methods.client.session.requestPermission, (ctx) => {
|
|
16769
16776
|
const optionId = (ctx.params.options.find((o) => o.kind === "allow_once" || o.kind === "allow_always") ?? ctx.params.options[0])?.optionId;
|
|
@@ -16772,19 +16779,52 @@ var AcpHost = class {
|
|
|
16772
16779
|
optionId
|
|
16773
16780
|
} : { outcome: "cancelled" } };
|
|
16774
16781
|
}).connect(stream);
|
|
16775
|
-
await this.conn.agent.request(methods.agent.initialize, {
|
|
16782
|
+
const init = await this.conn.agent.request(methods.agent.initialize, {
|
|
16776
16783
|
protocolVersion: PROTOCOL_VERSION,
|
|
16777
16784
|
clientCapabilities: { fs: {
|
|
16778
16785
|
readTextFile: false,
|
|
16779
16786
|
writeTextFile: false
|
|
16780
16787
|
} }
|
|
16781
16788
|
});
|
|
16789
|
+
this.canLoad = init.agentCapabilities?.loadSession ?? false;
|
|
16790
|
+
this.opts.log?.debug(`acp: agent initialized (loadSession capability=${this.canLoad})`);
|
|
16782
16791
|
}
|
|
16783
16792
|
async newSession(cwd) {
|
|
16784
|
-
|
|
16793
|
+
const res = await this.conn.agent.request(methods.agent.session.new, {
|
|
16785
16794
|
cwd,
|
|
16786
16795
|
mcpServers: []
|
|
16787
|
-
})
|
|
16796
|
+
});
|
|
16797
|
+
this.live.add(res.sessionId);
|
|
16798
|
+
return res.sessionId;
|
|
16799
|
+
}
|
|
16800
|
+
/** True iff THIS agent process created or loaded `sessionId` in its current lifetime. */
|
|
16801
|
+
hasSession(sessionId) {
|
|
16802
|
+
return this.live.has(sessionId);
|
|
16803
|
+
}
|
|
16804
|
+
/** Whether the agent advertised the `loadSession` capability (session/load is usable). */
|
|
16805
|
+
loadSupported() {
|
|
16806
|
+
return this.canLoad;
|
|
16807
|
+
}
|
|
16808
|
+
/** Resume a previously-created session by id (ACP `session/load`). The agent
|
|
16809
|
+
* restores its own history server-side; its replayed session/update stream is
|
|
16810
|
+
* suppressed so it isn't re-posted. Throws if the agent can't load the id
|
|
16811
|
+
* (caller falls back to newSession). */
|
|
16812
|
+
async loadSession(sessionId, cwd) {
|
|
16813
|
+
this.loadingSessions.add(sessionId);
|
|
16814
|
+
try {
|
|
16815
|
+
await this.conn.agent.request(methods.agent.session.load, {
|
|
16816
|
+
sessionId,
|
|
16817
|
+
cwd,
|
|
16818
|
+
mcpServers: []
|
|
16819
|
+
});
|
|
16820
|
+
this.live.add(sessionId);
|
|
16821
|
+
this.opts.log?.info(`acp: resumed session ${sessionId} via session/load`);
|
|
16822
|
+
} catch (err) {
|
|
16823
|
+
this.opts.log?.debug(`acp: session/load failed for ${sessionId} (${err.message}) — will recreate`);
|
|
16824
|
+
throw err;
|
|
16825
|
+
} finally {
|
|
16826
|
+
this.loadingSessions.delete(sessionId);
|
|
16827
|
+
}
|
|
16788
16828
|
}
|
|
16789
16829
|
async prompt(sessionId, blocks) {
|
|
16790
16830
|
return (await this.conn.agent.request(methods.agent.session.prompt, {
|
|
@@ -21140,7 +21180,7 @@ const defaultExec = (cmd, args) => new Promise((resolve) => {
|
|
|
21140
21180
|
/** macOS launchd controller. Writes a LaunchAgent plist that runs
|
|
21141
21181
|
* `<node> <cli-entry> run`, and drives it with `launchctl bootstrap/bootout`
|
|
21142
21182
|
* (falling back to legacy `load/unload` on older macOS). */
|
|
21143
|
-
const LABEL = "
|
|
21183
|
+
const LABEL = "md.agentconnect.daemon";
|
|
21144
21184
|
function buildPlist(a) {
|
|
21145
21185
|
const env = a.includeRootEnv ? ` <key>EnvironmentVariables</key>\n <dict>\n <key>AGENTCONNECT_ROOT</key>\n <string>${a.root}</string>\n </dict>\n` : "";
|
|
21146
21186
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -22948,12 +22988,75 @@ function watch$1(paths, options = {}) {
|
|
|
22948
22988
|
return watcher;
|
|
22949
22989
|
}
|
|
22950
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
|
|
22951
23029
|
//#region src/reconciler/reconciler.ts
|
|
22952
|
-
|
|
23030
|
+
/** Stable identity-free signature of an agent's effective config. Excludes the
|
|
23031
|
+
* loader-only `dir`/`env` fields (present on `LoadedAgent`) so the comparison is
|
|
23032
|
+
* over the agent's behaviour, not where it was found. Both sides are the LOADED
|
|
23033
|
+
* form (interpolated, path-absolutized), so this is apples-to-apples and stable
|
|
23034
|
+
* across re-serialization (zod key order is fixed by the schema). */
|
|
23035
|
+
function signature(a) {
|
|
23036
|
+
const { dir, env, ...rest } = a;
|
|
23037
|
+
return JSON.stringify(rest);
|
|
23038
|
+
}
|
|
23039
|
+
/**
|
|
23040
|
+
* Diff desired (freshly loaded active agents) against the running set.
|
|
23041
|
+
* - `toStart` — desired ids not currently running.
|
|
23042
|
+
* - `toStop` — running ids no longer desired.
|
|
23043
|
+
* - `toRestart` — same id, changed config (runtime/workspace/integrations/…): the
|
|
23044
|
+
* host must be evicted so the next session picks up fresh config (design §5.2).
|
|
23045
|
+
* Without this, an in-place edit to an existing agent.json is silently a no-op.
|
|
23046
|
+
*/
|
|
23047
|
+
function diffAgents(desired, actual) {
|
|
22953
23048
|
const desiredIds = new Set(desired.map((a) => a.id));
|
|
23049
|
+
const toStart = [];
|
|
23050
|
+
const toRestart = [];
|
|
23051
|
+
for (const a of desired) {
|
|
23052
|
+
const cur = actual.get(a.id);
|
|
23053
|
+
if (!cur) toStart.push(a);
|
|
23054
|
+
else if (signature(cur) !== signature(a)) toRestart.push(a);
|
|
23055
|
+
}
|
|
22954
23056
|
return {
|
|
22955
|
-
toStart
|
|
22956
|
-
toStop:
|
|
23057
|
+
toStart,
|
|
23058
|
+
toStop: [...actual.keys()].filter((id) => !desiredIds.has(id)),
|
|
23059
|
+
toRestart
|
|
22957
23060
|
};
|
|
22958
23061
|
}
|
|
22959
23062
|
//#endregion
|
|
@@ -22980,6 +23083,10 @@ var LocalStore = class {
|
|
|
22980
23083
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
22981
23084
|
routingEpoch INTEGER, assignments TEXT, globalRules TEXT
|
|
22982
23085
|
);
|
|
23086
|
+
CREATE TABLE IF NOT EXISTS cp_agents (
|
|
23087
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
23088
|
+
specs TEXT
|
|
23089
|
+
);
|
|
22983
23090
|
`);
|
|
22984
23091
|
}
|
|
22985
23092
|
getSession(key) {
|
|
@@ -23013,6 +23120,13 @@ var LocalStore = class {
|
|
|
23013
23120
|
globalRules
|
|
23014
23121
|
});
|
|
23015
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
|
+
}
|
|
23016
23130
|
close() {
|
|
23017
23131
|
this.db.close();
|
|
23018
23132
|
}
|
|
@@ -23057,6 +23171,24 @@ var SessionManager = class {
|
|
|
23057
23171
|
updatedAt: Date.now()
|
|
23058
23172
|
};
|
|
23059
23173
|
this.deps.store.upsertSession(rec);
|
|
23174
|
+
} else if (host.hasSession?.(rec.acpSessionId) === false) {
|
|
23175
|
+
const cwd = await prepareWorkspace(agent);
|
|
23176
|
+
let resumed = false;
|
|
23177
|
+
if (host.loadSupported?.()) try {
|
|
23178
|
+
await host.loadSession(rec.acpSessionId, cwd);
|
|
23179
|
+
resumed = true;
|
|
23180
|
+
} catch {}
|
|
23181
|
+
if (!resumed) {
|
|
23182
|
+
const acpSessionId = await host.newSession(cwd);
|
|
23183
|
+
rec = {
|
|
23184
|
+
...rec,
|
|
23185
|
+
acpSessionId,
|
|
23186
|
+
state: "idle",
|
|
23187
|
+
lastDeliveredTs: null,
|
|
23188
|
+
updatedAt: Date.now()
|
|
23189
|
+
};
|
|
23190
|
+
this.deps.store.upsertSession(rec);
|
|
23191
|
+
}
|
|
23060
23192
|
}
|
|
23061
23193
|
const gap = this.deps.store.transcriptSince(msg.channel, thread, rec.lastDeliveredTs);
|
|
23062
23194
|
const blocks = [];
|
|
@@ -23128,11 +23260,8 @@ function routeRules(msg, rules, threadOwner) {
|
|
|
23128
23260
|
if (msg.thread) {
|
|
23129
23261
|
const owner = threadOwner(msg.channel, msg.thread);
|
|
23130
23262
|
if (owner) {
|
|
23131
|
-
const
|
|
23132
|
-
if (
|
|
23133
|
-
if (reachable.size === 1) return pickRule(scopeCandidates.find((x) => x.agentId === owner));
|
|
23134
|
-
return null;
|
|
23135
|
-
}
|
|
23263
|
+
const ownerRule = scopeCandidates.find((x) => x.agentId === owner);
|
|
23264
|
+
if (ownerRule) return pickRule(ownerRule);
|
|
23136
23265
|
}
|
|
23137
23266
|
}
|
|
23138
23267
|
const layer = kindCandidates.some((r) => r.source === "cp" && r.scope.channel === msg.channel && (r.scope.thread === void 0 || r.scope.thread === msg.thread)) ? kindCandidates.filter((r) => r.source === "cp") : kindCandidates;
|
|
@@ -80456,6 +80585,14 @@ var CpClient = class {
|
|
|
80456
80585
|
case "route/update":
|
|
80457
80586
|
this.deps.configApply.applyRouteUpdate(frame.payload);
|
|
80458
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;
|
|
80459
80596
|
case "agent/launch":
|
|
80460
80597
|
case "agent/stop":
|
|
80461
80598
|
case "agent/prompt":
|
|
@@ -80509,6 +80646,46 @@ var CpCronRegistry = class {
|
|
|
80509
80646
|
}
|
|
80510
80647
|
};
|
|
80511
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
|
|
80512
80689
|
//#region src/cp/config-apply.ts
|
|
80513
80690
|
const LOG_LEVELS = /* @__PURE__ */ new Set([
|
|
80514
80691
|
"trace",
|
|
@@ -80577,16 +80754,23 @@ var SystemClock = class {
|
|
|
80577
80754
|
const systemClock = new SystemClock();
|
|
80578
80755
|
//#endregion
|
|
80579
80756
|
//#region src/daemon.ts
|
|
80580
|
-
|
|
80581
|
-
|
|
80582
|
-
|
|
80583
|
-
|
|
80584
|
-
|
|
80757
|
+
/** Format an error for logs, surfacing a JSON-RPC/ACP RequestError's `code` and
|
|
80758
|
+
* `data` — for an agent-side `Internal error` the actionable detail (the adapter's
|
|
80759
|
+
* underlying exception) lives in `data`, which a bare `.stack` discards. */
|
|
80760
|
+
function formatErr(err) {
|
|
80761
|
+
const e = err;
|
|
80762
|
+
if (e && typeof e.code === "number") {
|
|
80763
|
+
const data = e.data === void 0 ? "" : ` data=${typeof e.data === "string" ? e.data : JSON.stringify(e.data)}`;
|
|
80764
|
+
return `${e.name ?? "Error"}: ${e.message ?? ""} (code=${e.code})${data}`;
|
|
80765
|
+
}
|
|
80766
|
+
return e?.stack ?? String(err);
|
|
80767
|
+
}
|
|
80585
80768
|
const MAX_QUEUED_PER_SESSION = 10;
|
|
80586
80769
|
var Daemon = class {
|
|
80587
80770
|
opts;
|
|
80588
80771
|
store;
|
|
80589
80772
|
agents = /* @__PURE__ */ new Map();
|
|
80773
|
+
fileAgents = /* @__PURE__ */ new Map();
|
|
80590
80774
|
hosts = /* @__PURE__ */ new Map();
|
|
80591
80775
|
connections = [];
|
|
80592
80776
|
scheduler;
|
|
@@ -80603,6 +80787,7 @@ var Daemon = class {
|
|
|
80603
80787
|
runtimeNames = {};
|
|
80604
80788
|
cpClient;
|
|
80605
80789
|
cpCrons;
|
|
80790
|
+
cpAgents;
|
|
80606
80791
|
botUserIds = {};
|
|
80607
80792
|
cpRouting;
|
|
80608
80793
|
constructor(opts = {}) {
|
|
@@ -80627,7 +80812,7 @@ var Daemon = class {
|
|
|
80627
80812
|
this.log.info(`control plane: ${cfg.controlPlane?.enabled ? `enabled (${cfg.controlPlane.url ?? "no url"})` : "disabled — running local"}`);
|
|
80628
80813
|
this.agentsDir = cfg.agentsDir;
|
|
80629
80814
|
const agents = this.loadAgentList();
|
|
80630
|
-
|
|
80815
|
+
this.fileAgents = new Map(agents.map((a) => [a.id, a]));
|
|
80631
80816
|
this.log.info(`loaded ${agents.length} agent(s) from ${this.agentsDir}${agents.length ? `: ${agents.map((a) => a.id).join(", ")}` : ""}`);
|
|
80632
80817
|
this.root = root;
|
|
80633
80818
|
const resolvedRuntimes = await resolveRuntimes(cfg, root, {
|
|
@@ -80651,13 +80836,21 @@ var Daemon = class {
|
|
|
80651
80836
|
},
|
|
80652
80837
|
save: (s) => this.store.setCpRouting(s.routingEpoch, JSON.stringify(s.assignments), JSON.stringify(s.globalRules))
|
|
80653
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);
|
|
80654
80847
|
this.sessions = new SessionManager({
|
|
80655
80848
|
store: this.store,
|
|
80656
80849
|
hostFor: (agentId) => this.ensureHostAsync(agentId),
|
|
80657
80850
|
agentById: (id) => this.agents.get(id)
|
|
80658
80851
|
});
|
|
80659
80852
|
this.scheduler = new Scheduler({
|
|
80660
|
-
onFire: (agentId, msg) => void this.dispatch(agentId, msg).catch((err) => this.log.error(`cron dispatch failed for agent "${agentId}": ${err
|
|
80853
|
+
onFire: (agentId, msg) => void this.dispatch(agentId, msg).catch((err) => this.log.error(`cron dispatch failed for agent "${agentId}": ${formatErr(err)}`)),
|
|
80661
80854
|
newTraceId: () => randomUUID()
|
|
80662
80855
|
});
|
|
80663
80856
|
const groups = consolidate(agents);
|
|
@@ -80706,8 +80899,54 @@ var Daemon = class {
|
|
|
80706
80899
|
loadAgentList() {
|
|
80707
80900
|
return this.opts.agentName ? [selectAgent(this.agentsDir, this.opts.agentName)] : loadAgents(this.agentsDir);
|
|
80708
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;
|
|
80709
80927
|
async reconcile() {
|
|
80710
|
-
|
|
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();
|
|
80947
|
+
const { toStart, toStop, toRestart } = diffAgents(desired, this.agents);
|
|
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(", ")}]`);
|
|
80949
|
+
else this.log.debug(`reconcile: no changes (${desired.length} desired agent(s))`);
|
|
80711
80950
|
for (const id of toStop) {
|
|
80712
80951
|
const host = this.hosts.get(id);
|
|
80713
80952
|
if (host) {
|
|
@@ -80717,6 +80956,15 @@ var Daemon = class {
|
|
|
80717
80956
|
this.hostStarts.delete(id);
|
|
80718
80957
|
this.agents.delete(id);
|
|
80719
80958
|
}
|
|
80959
|
+
for (const a of toRestart) {
|
|
80960
|
+
const host = this.hosts.get(a.id);
|
|
80961
|
+
if (host) {
|
|
80962
|
+
await host.stop();
|
|
80963
|
+
this.hosts.delete(a.id);
|
|
80964
|
+
}
|
|
80965
|
+
this.hostStarts.delete(a.id);
|
|
80966
|
+
this.agents.set(a.id, a);
|
|
80967
|
+
}
|
|
80720
80968
|
for (const a of toStart) this.agents.set(a.id, a);
|
|
80721
80969
|
}
|
|
80722
80970
|
ensureHost(agentId, cfg) {
|
|
@@ -80730,7 +80978,11 @@ var Daemon = class {
|
|
|
80730
80978
|
if (!runtime) throw new Error(`runtime "${agent.runtime}" not available: not installed on this host, or absent from config.runtimes / the ACP registry`);
|
|
80731
80979
|
host = new AcpHost(runtime, {
|
|
80732
80980
|
onUpdate,
|
|
80733
|
-
env:
|
|
80981
|
+
env: {
|
|
80982
|
+
...agentChildEnv(agent),
|
|
80983
|
+
...cpRuntimeEnv(agent)
|
|
80984
|
+
},
|
|
80985
|
+
log: this.log
|
|
80734
80986
|
});
|
|
80735
80987
|
}
|
|
80736
80988
|
this.hosts.set(agentId, host);
|
|
@@ -80755,7 +81007,7 @@ var Daemon = class {
|
|
|
80755
81007
|
return;
|
|
80756
81008
|
}
|
|
80757
81009
|
this.log.info(`routing: ch=${msg.channel} → agent "${result.agentId}" (integration ${result.integrationId})`);
|
|
80758
|
-
this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${err
|
|
81010
|
+
this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
|
|
80759
81011
|
}
|
|
80760
81012
|
queued = /* @__PURE__ */ new Map();
|
|
80761
81013
|
/**
|
|
@@ -80833,10 +81085,15 @@ var Daemon = class {
|
|
|
80833
81085
|
resolveCpAgent(agentId) {
|
|
80834
81086
|
return resolveAgentIntegration(this.agents.get(agentId), this.botUserIds);
|
|
80835
81087
|
}
|
|
80836
|
-
/**
|
|
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
|
+
*/
|
|
80837
81093
|
cpDegradedScopes() {
|
|
80838
81094
|
const out = /* @__PURE__ */ new Set();
|
|
80839
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);
|
|
80840
81097
|
return [...out];
|
|
80841
81098
|
}
|
|
80842
81099
|
async dispatch(agentId, msg, integrationId) {
|
|
@@ -80844,7 +81101,7 @@ var Daemon = class {
|
|
|
80844
81101
|
const replyConn = this.replyConnFor(agentId, integrationId);
|
|
80845
81102
|
const wasRunning = this.hostStarts.has(agentId);
|
|
80846
81103
|
const statusThread = msg.thread ?? msg.msgId;
|
|
80847
|
-
replyConn?.setStatus(msg.channel, statusThread, wasRunning ? "is thinking…" : "is starting up…"
|
|
81104
|
+
replyConn?.setStatus(msg.channel, statusThread, wasRunning ? "is thinking…" : "is starting up…");
|
|
80848
81105
|
const { sessionId, blocks } = await this.sessions.handle(agentId, msg);
|
|
80849
81106
|
this.pending.set(sessionId, {
|
|
80850
81107
|
conv,
|
|
@@ -80854,7 +81111,7 @@ var Daemon = class {
|
|
|
80854
81111
|
});
|
|
80855
81112
|
try {
|
|
80856
81113
|
const host = await this.ensureHostAsync(agentId);
|
|
80857
|
-
if (!wasRunning) replyConn?.setStatus(msg.channel, statusThread, "is thinking…"
|
|
81114
|
+
if (!wasRunning) replyConn?.setStatus(msg.channel, statusThread, "is thinking…");
|
|
80858
81115
|
await host.prompt(sessionId, blocks);
|
|
80859
81116
|
for (const action of conv.onFinal(`local://session/${sessionId}`)) await this.applyAction(action, replyConn, msg.channel, statusThread);
|
|
80860
81117
|
} finally {
|
|
@@ -80862,10 +81119,10 @@ var Daemon = class {
|
|
|
80862
81119
|
}
|
|
80863
81120
|
this.flushQueued(agentId, sessionId, integrationId);
|
|
80864
81121
|
}
|
|
80865
|
-
/** Route a converger action: set-status → setStatus (
|
|
81122
|
+
/** Route a converger action: set-status → setStatus (status text only; '' clears); else postMessage. */
|
|
80866
81123
|
async applyAction(action, conn, channel, thread) {
|
|
80867
81124
|
if (action.kind === "set-status") {
|
|
80868
|
-
if (conn && thread) await conn.setStatus(channel, thread, action.text
|
|
81125
|
+
if (conn && thread) await conn.setStatus(channel, thread, action.text);
|
|
80869
81126
|
return;
|
|
80870
81127
|
}
|
|
80871
81128
|
await conn?.postMessage(channel, action.text, thread);
|
|
@@ -80900,6 +81157,7 @@ var Daemon = class {
|
|
|
80900
81157
|
},
|
|
80901
81158
|
applyReconcileSnapshot: (snap) => {
|
|
80902
81159
|
this.cpCrons?.converge(snap.crons);
|
|
81160
|
+
this.cpAgents?.converge(snap.agents);
|
|
80903
81161
|
this.cpRouting?.converge({
|
|
80904
81162
|
routingEpoch: snap.routingEpoch,
|
|
80905
81163
|
assignments: snap.assignments,
|
|
@@ -80907,7 +81165,10 @@ var Daemon = class {
|
|
|
80907
81165
|
});
|
|
80908
81166
|
if (snap.leases.length) this.log.debug(`cp: ${snap.leases.length} lease(s) noted (secrets handled later)`);
|
|
80909
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)`);
|
|
80910
81169
|
},
|
|
81170
|
+
applyAgentUpsert: (agentId, spec) => this.cpAgents?.upsert(agentId, spec),
|
|
81171
|
+
applyAgentRemove: (agentId) => this.cpAgents?.remove(agentId),
|
|
80911
81172
|
upsertCron: (cron) => this.cpCrons.upsert(cron),
|
|
80912
81173
|
removeCron: (cronId) => this.cpCrons.remove(cronId),
|
|
80913
81174
|
applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),
|