@agentconnect.md/daemon 1.0.0-rc.23 → 1.0.0-rc.24
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 +121 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16741,6 +16741,9 @@ var AcpHost = class {
|
|
|
16741
16741
|
opts;
|
|
16742
16742
|
child;
|
|
16743
16743
|
conn;
|
|
16744
|
+
live = /* @__PURE__ */ new Set();
|
|
16745
|
+
loadingSessions = /* @__PURE__ */ new Set();
|
|
16746
|
+
canLoad = false;
|
|
16744
16747
|
constructor(runtime, opts) {
|
|
16745
16748
|
this.runtime = runtime;
|
|
16746
16749
|
this.opts = opts;
|
|
@@ -16764,6 +16767,7 @@ var AcpHost = class {
|
|
|
16764
16767
|
const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
|
|
16765
16768
|
const self = this;
|
|
16766
16769
|
this.conn = client({ name: "agentconnect" }).onNotification(methods.client.session.update, (ctx) => {
|
|
16770
|
+
if (self.loadingSessions.has(ctx.params.sessionId)) return;
|
|
16767
16771
|
self.opts.onUpdate(ctx.params.sessionId, ctx.params.update);
|
|
16768
16772
|
}).onRequest(methods.client.session.requestPermission, (ctx) => {
|
|
16769
16773
|
const optionId = (ctx.params.options.find((o) => o.kind === "allow_once" || o.kind === "allow_always") ?? ctx.params.options[0])?.optionId;
|
|
@@ -16772,19 +16776,52 @@ var AcpHost = class {
|
|
|
16772
16776
|
optionId
|
|
16773
16777
|
} : { outcome: "cancelled" } };
|
|
16774
16778
|
}).connect(stream);
|
|
16775
|
-
await this.conn.agent.request(methods.agent.initialize, {
|
|
16779
|
+
const init = await this.conn.agent.request(methods.agent.initialize, {
|
|
16776
16780
|
protocolVersion: PROTOCOL_VERSION,
|
|
16777
16781
|
clientCapabilities: { fs: {
|
|
16778
16782
|
readTextFile: false,
|
|
16779
16783
|
writeTextFile: false
|
|
16780
16784
|
} }
|
|
16781
16785
|
});
|
|
16786
|
+
this.canLoad = init.agentCapabilities?.loadSession ?? false;
|
|
16787
|
+
this.opts.log?.debug(`acp: agent initialized (loadSession capability=${this.canLoad})`);
|
|
16782
16788
|
}
|
|
16783
16789
|
async newSession(cwd) {
|
|
16784
|
-
|
|
16790
|
+
const res = await this.conn.agent.request(methods.agent.session.new, {
|
|
16785
16791
|
cwd,
|
|
16786
16792
|
mcpServers: []
|
|
16787
|
-
})
|
|
16793
|
+
});
|
|
16794
|
+
this.live.add(res.sessionId);
|
|
16795
|
+
return res.sessionId;
|
|
16796
|
+
}
|
|
16797
|
+
/** True iff THIS agent process created or loaded `sessionId` in its current lifetime. */
|
|
16798
|
+
hasSession(sessionId) {
|
|
16799
|
+
return this.live.has(sessionId);
|
|
16800
|
+
}
|
|
16801
|
+
/** Whether the agent advertised the `loadSession` capability (session/load is usable). */
|
|
16802
|
+
loadSupported() {
|
|
16803
|
+
return this.canLoad;
|
|
16804
|
+
}
|
|
16805
|
+
/** Resume a previously-created session by id (ACP `session/load`). The agent
|
|
16806
|
+
* restores its own history server-side; its replayed session/update stream is
|
|
16807
|
+
* suppressed so it isn't re-posted. Throws if the agent can't load the id
|
|
16808
|
+
* (caller falls back to newSession). */
|
|
16809
|
+
async loadSession(sessionId, cwd) {
|
|
16810
|
+
this.loadingSessions.add(sessionId);
|
|
16811
|
+
try {
|
|
16812
|
+
await this.conn.agent.request(methods.agent.session.load, {
|
|
16813
|
+
sessionId,
|
|
16814
|
+
cwd,
|
|
16815
|
+
mcpServers: []
|
|
16816
|
+
});
|
|
16817
|
+
this.live.add(sessionId);
|
|
16818
|
+
this.opts.log?.info(`acp: resumed session ${sessionId} via session/load`);
|
|
16819
|
+
} catch (err) {
|
|
16820
|
+
this.opts.log?.debug(`acp: session/load failed for ${sessionId} (${err.message}) — will recreate`);
|
|
16821
|
+
throw err;
|
|
16822
|
+
} finally {
|
|
16823
|
+
this.loadingSessions.delete(sessionId);
|
|
16824
|
+
}
|
|
16788
16825
|
}
|
|
16789
16826
|
async prompt(sessionId, blocks) {
|
|
16790
16827
|
return (await this.conn.agent.request(methods.agent.session.prompt, {
|
|
@@ -21140,7 +21177,7 @@ const defaultExec = (cmd, args) => new Promise((resolve) => {
|
|
|
21140
21177
|
/** macOS launchd controller. Writes a LaunchAgent plist that runs
|
|
21141
21178
|
* `<node> <cli-entry> run`, and drives it with `launchctl bootstrap/bootout`
|
|
21142
21179
|
* (falling back to legacy `load/unload` on older macOS). */
|
|
21143
|
-
const LABEL = "
|
|
21180
|
+
const LABEL = "md.agentconnect.daemon";
|
|
21144
21181
|
function buildPlist(a) {
|
|
21145
21182
|
const env = a.includeRootEnv ? ` <key>EnvironmentVariables</key>\n <dict>\n <key>AGENTCONNECT_ROOT</key>\n <string>${a.root}</string>\n </dict>\n` : "";
|
|
21146
21183
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -22949,11 +22986,36 @@ function watch$1(paths, options = {}) {
|
|
|
22949
22986
|
}
|
|
22950
22987
|
//#endregion
|
|
22951
22988
|
//#region src/reconciler/reconciler.ts
|
|
22952
|
-
|
|
22989
|
+
/** Stable identity-free signature of an agent's effective config. Excludes the
|
|
22990
|
+
* loader-only `dir`/`env` fields (present on `LoadedAgent`) so the comparison is
|
|
22991
|
+
* over the agent's behaviour, not where it was found. Both sides are the LOADED
|
|
22992
|
+
* form (interpolated, path-absolutized), so this is apples-to-apples and stable
|
|
22993
|
+
* across re-serialization (zod key order is fixed by the schema). */
|
|
22994
|
+
function signature(a) {
|
|
22995
|
+
const { dir, env, ...rest } = a;
|
|
22996
|
+
return JSON.stringify(rest);
|
|
22997
|
+
}
|
|
22998
|
+
/**
|
|
22999
|
+
* Diff desired (freshly loaded active agents) against the running set.
|
|
23000
|
+
* - `toStart` — desired ids not currently running.
|
|
23001
|
+
* - `toStop` — running ids no longer desired.
|
|
23002
|
+
* - `toRestart` — same id, changed config (runtime/workspace/integrations/…): the
|
|
23003
|
+
* host must be evicted so the next session picks up fresh config (design §5.2).
|
|
23004
|
+
* Without this, an in-place edit to an existing agent.json is silently a no-op.
|
|
23005
|
+
*/
|
|
23006
|
+
function diffAgents(desired, actual) {
|
|
22953
23007
|
const desiredIds = new Set(desired.map((a) => a.id));
|
|
23008
|
+
const toStart = [];
|
|
23009
|
+
const toRestart = [];
|
|
23010
|
+
for (const a of desired) {
|
|
23011
|
+
const cur = actual.get(a.id);
|
|
23012
|
+
if (!cur) toStart.push(a);
|
|
23013
|
+
else if (signature(cur) !== signature(a)) toRestart.push(a);
|
|
23014
|
+
}
|
|
22954
23015
|
return {
|
|
22955
|
-
toStart
|
|
22956
|
-
toStop:
|
|
23016
|
+
toStart,
|
|
23017
|
+
toStop: [...actual.keys()].filter((id) => !desiredIds.has(id)),
|
|
23018
|
+
toRestart
|
|
22957
23019
|
};
|
|
22958
23020
|
}
|
|
22959
23021
|
//#endregion
|
|
@@ -23057,6 +23119,24 @@ var SessionManager = class {
|
|
|
23057
23119
|
updatedAt: Date.now()
|
|
23058
23120
|
};
|
|
23059
23121
|
this.deps.store.upsertSession(rec);
|
|
23122
|
+
} else if (host.hasSession?.(rec.acpSessionId) === false) {
|
|
23123
|
+
const cwd = await prepareWorkspace(agent);
|
|
23124
|
+
let resumed = false;
|
|
23125
|
+
if (host.loadSupported?.()) try {
|
|
23126
|
+
await host.loadSession(rec.acpSessionId, cwd);
|
|
23127
|
+
resumed = true;
|
|
23128
|
+
} catch {}
|
|
23129
|
+
if (!resumed) {
|
|
23130
|
+
const acpSessionId = await host.newSession(cwd);
|
|
23131
|
+
rec = {
|
|
23132
|
+
...rec,
|
|
23133
|
+
acpSessionId,
|
|
23134
|
+
state: "idle",
|
|
23135
|
+
lastDeliveredTs: null,
|
|
23136
|
+
updatedAt: Date.now()
|
|
23137
|
+
};
|
|
23138
|
+
this.deps.store.upsertSession(rec);
|
|
23139
|
+
}
|
|
23060
23140
|
}
|
|
23061
23141
|
const gap = this.deps.store.transcriptSince(msg.channel, thread, rec.lastDeliveredTs);
|
|
23062
23142
|
const blocks = [];
|
|
@@ -23128,11 +23208,8 @@ function routeRules(msg, rules, threadOwner) {
|
|
|
23128
23208
|
if (msg.thread) {
|
|
23129
23209
|
const owner = threadOwner(msg.channel, msg.thread);
|
|
23130
23210
|
if (owner) {
|
|
23131
|
-
const
|
|
23132
|
-
if (
|
|
23133
|
-
if (reachable.size === 1) return pickRule(scopeCandidates.find((x) => x.agentId === owner));
|
|
23134
|
-
return null;
|
|
23135
|
-
}
|
|
23211
|
+
const ownerRule = scopeCandidates.find((x) => x.agentId === owner);
|
|
23212
|
+
if (ownerRule) return pickRule(ownerRule);
|
|
23136
23213
|
}
|
|
23137
23214
|
}
|
|
23138
23215
|
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;
|
|
@@ -80577,11 +80654,17 @@ var SystemClock = class {
|
|
|
80577
80654
|
const systemClock = new SystemClock();
|
|
80578
80655
|
//#endregion
|
|
80579
80656
|
//#region src/daemon.ts
|
|
80580
|
-
|
|
80581
|
-
|
|
80582
|
-
|
|
80583
|
-
|
|
80584
|
-
|
|
80657
|
+
/** Format an error for logs, surfacing a JSON-RPC/ACP RequestError's `code` and
|
|
80658
|
+
* `data` — for an agent-side `Internal error` the actionable detail (the adapter's
|
|
80659
|
+
* underlying exception) lives in `data`, which a bare `.stack` discards. */
|
|
80660
|
+
function formatErr(err) {
|
|
80661
|
+
const e = err;
|
|
80662
|
+
if (e && typeof e.code === "number") {
|
|
80663
|
+
const data = e.data === void 0 ? "" : ` data=${typeof e.data === "string" ? e.data : JSON.stringify(e.data)}`;
|
|
80664
|
+
return `${e.name ?? "Error"}: ${e.message ?? ""} (code=${e.code})${data}`;
|
|
80665
|
+
}
|
|
80666
|
+
return e?.stack ?? String(err);
|
|
80667
|
+
}
|
|
80585
80668
|
const MAX_QUEUED_PER_SESSION = 10;
|
|
80586
80669
|
var Daemon = class {
|
|
80587
80670
|
opts;
|
|
@@ -80657,7 +80740,7 @@ var Daemon = class {
|
|
|
80657
80740
|
agentById: (id) => this.agents.get(id)
|
|
80658
80741
|
});
|
|
80659
80742
|
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
|
|
80743
|
+
onFire: (agentId, msg) => void this.dispatch(agentId, msg).catch((err) => this.log.error(`cron dispatch failed for agent "${agentId}": ${formatErr(err)}`)),
|
|
80661
80744
|
newTraceId: () => randomUUID()
|
|
80662
80745
|
});
|
|
80663
80746
|
const groups = consolidate(agents);
|
|
@@ -80707,7 +80790,10 @@ var Daemon = class {
|
|
|
80707
80790
|
return this.opts.agentName ? [selectAgent(this.agentsDir, this.opts.agentName)] : loadAgents(this.agentsDir);
|
|
80708
80791
|
}
|
|
80709
80792
|
async reconcile() {
|
|
80710
|
-
const
|
|
80793
|
+
const desired = this.loadAgentList();
|
|
80794
|
+
const { toStart, toStop, toRestart } = diffAgents(desired, this.agents);
|
|
80795
|
+
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
|
+
else this.log.debug(`reconcile: no changes (${desired.length} desired agent(s))`);
|
|
80711
80797
|
for (const id of toStop) {
|
|
80712
80798
|
const host = this.hosts.get(id);
|
|
80713
80799
|
if (host) {
|
|
@@ -80717,6 +80803,15 @@ var Daemon = class {
|
|
|
80717
80803
|
this.hostStarts.delete(id);
|
|
80718
80804
|
this.agents.delete(id);
|
|
80719
80805
|
}
|
|
80806
|
+
for (const a of toRestart) {
|
|
80807
|
+
const host = this.hosts.get(a.id);
|
|
80808
|
+
if (host) {
|
|
80809
|
+
await host.stop();
|
|
80810
|
+
this.hosts.delete(a.id);
|
|
80811
|
+
}
|
|
80812
|
+
this.hostStarts.delete(a.id);
|
|
80813
|
+
this.agents.set(a.id, a);
|
|
80814
|
+
}
|
|
80720
80815
|
for (const a of toStart) this.agents.set(a.id, a);
|
|
80721
80816
|
}
|
|
80722
80817
|
ensureHost(agentId, cfg) {
|
|
@@ -80730,7 +80825,8 @@ var Daemon = class {
|
|
|
80730
80825
|
if (!runtime) throw new Error(`runtime "${agent.runtime}" not available: not installed on this host, or absent from config.runtimes / the ACP registry`);
|
|
80731
80826
|
host = new AcpHost(runtime, {
|
|
80732
80827
|
onUpdate,
|
|
80733
|
-
env: agentChildEnv(agent)
|
|
80828
|
+
env: agentChildEnv(agent),
|
|
80829
|
+
log: this.log
|
|
80734
80830
|
});
|
|
80735
80831
|
}
|
|
80736
80832
|
this.hosts.set(agentId, host);
|
|
@@ -80755,7 +80851,7 @@ var Daemon = class {
|
|
|
80755
80851
|
return;
|
|
80756
80852
|
}
|
|
80757
80853
|
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
|
|
80854
|
+
this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
|
|
80759
80855
|
}
|
|
80760
80856
|
queued = /* @__PURE__ */ new Map();
|
|
80761
80857
|
/**
|
|
@@ -80844,7 +80940,7 @@ var Daemon = class {
|
|
|
80844
80940
|
const replyConn = this.replyConnFor(agentId, integrationId);
|
|
80845
80941
|
const wasRunning = this.hostStarts.has(agentId);
|
|
80846
80942
|
const statusThread = msg.thread ?? msg.msgId;
|
|
80847
|
-
replyConn?.setStatus(msg.channel, statusThread, wasRunning ? "is thinking…" : "is starting up…"
|
|
80943
|
+
replyConn?.setStatus(msg.channel, statusThread, wasRunning ? "is thinking…" : "is starting up…");
|
|
80848
80944
|
const { sessionId, blocks } = await this.sessions.handle(agentId, msg);
|
|
80849
80945
|
this.pending.set(sessionId, {
|
|
80850
80946
|
conv,
|
|
@@ -80854,7 +80950,7 @@ var Daemon = class {
|
|
|
80854
80950
|
});
|
|
80855
80951
|
try {
|
|
80856
80952
|
const host = await this.ensureHostAsync(agentId);
|
|
80857
|
-
if (!wasRunning) replyConn?.setStatus(msg.channel, statusThread, "is thinking…"
|
|
80953
|
+
if (!wasRunning) replyConn?.setStatus(msg.channel, statusThread, "is thinking…");
|
|
80858
80954
|
await host.prompt(sessionId, blocks);
|
|
80859
80955
|
for (const action of conv.onFinal(`local://session/${sessionId}`)) await this.applyAction(action, replyConn, msg.channel, statusThread);
|
|
80860
80956
|
} finally {
|
|
@@ -80862,10 +80958,10 @@ var Daemon = class {
|
|
|
80862
80958
|
}
|
|
80863
80959
|
this.flushQueued(agentId, sessionId, integrationId);
|
|
80864
80960
|
}
|
|
80865
|
-
/** Route a converger action: set-status → setStatus (
|
|
80961
|
+
/** Route a converger action: set-status → setStatus (status text only; '' clears); else postMessage. */
|
|
80866
80962
|
async applyAction(action, conn, channel, thread) {
|
|
80867
80963
|
if (action.kind === "set-status") {
|
|
80868
|
-
if (conn && thread) await conn.setStatus(channel, thread, action.text
|
|
80964
|
+
if (conn && thread) await conn.setStatus(channel, thread, action.text);
|
|
80869
80965
|
return;
|
|
80870
80966
|
}
|
|
80871
80967
|
await conn?.postMessage(channel, action.text, thread);
|