@agentconnect.md/daemon 1.0.0-rc.24 → 1.0.0-rc.26
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/bridge-Dj8U1Jp7.js +9900 -0
- package/dist/bridge-Dj8U1Jp7.js.map +1 -0
- package/dist/index.js +804 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { EventEmitter } from "node:events";
|
|
|
4
4
|
import childProcess, { execFile, spawn } from "node:child_process";
|
|
5
5
|
import * as sp from "node:path";
|
|
6
6
|
import path, { delimiter, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
7
|
-
import fs, { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, stat, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
|
|
7
|
+
import fs, { accessSync, chmodSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, stat, unlinkSync, unwatchFile, watch, watchFile, writeFileSync } from "node:fs";
|
|
8
8
|
import process$1 from "node:process";
|
|
9
9
|
import { stripVTControlCharacters } from "node:util";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -12,9 +12,10 @@ import { createInterface } from "node:readline";
|
|
|
12
12
|
import { freemem, homedir, hostname, loadavg, totalmem, type } from "node:os";
|
|
13
13
|
import { spawn as spawn$1 } from "child_process";
|
|
14
14
|
import { Readable, Writable } from "node:stream";
|
|
15
|
-
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
16
16
|
import { lstat, open, readdir, realpath, stat as stat$1 } from "node:fs/promises";
|
|
17
17
|
import { DatabaseSync } from "node:sqlite";
|
|
18
|
+
import net from "node:net";
|
|
18
19
|
//#region \0rolldown/runtime.js
|
|
19
20
|
var __create = Object.create;
|
|
20
21
|
var __defProp$1 = Object.defineProperty;
|
|
@@ -3599,6 +3600,7 @@ const string$1 = (params) => {
|
|
|
3599
3600
|
const integer = /^-?\d+$/;
|
|
3600
3601
|
const number$1 = /^-?\d+(?:\.\d+)?$/;
|
|
3601
3602
|
const boolean$1 = /^(?:true|false)$/i;
|
|
3603
|
+
const _null$2 = /^null$/i;
|
|
3602
3604
|
const lowercase = /^[^A-Z]*$/;
|
|
3603
3605
|
const uppercase = /^[^a-z]*$/;
|
|
3604
3606
|
//#endregion
|
|
@@ -4411,6 +4413,22 @@ const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
|
|
|
4411
4413
|
return payload;
|
|
4412
4414
|
};
|
|
4413
4415
|
});
|
|
4416
|
+
const $ZodNull = /*@__PURE__*/ $constructor("$ZodNull", (inst, def) => {
|
|
4417
|
+
$ZodType.init(inst, def);
|
|
4418
|
+
inst._zod.pattern = _null$2;
|
|
4419
|
+
inst._zod.values = /* @__PURE__ */ new Set([null]);
|
|
4420
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
4421
|
+
const input = payload.value;
|
|
4422
|
+
if (input === null) return payload;
|
|
4423
|
+
payload.issues.push({
|
|
4424
|
+
expected: "null",
|
|
4425
|
+
code: "invalid_type",
|
|
4426
|
+
input,
|
|
4427
|
+
inst
|
|
4428
|
+
});
|
|
4429
|
+
return payload;
|
|
4430
|
+
};
|
|
4431
|
+
});
|
|
4414
4432
|
const $ZodUnknown = /*@__PURE__*/ $constructor("$ZodUnknown", (inst, def) => {
|
|
4415
4433
|
$ZodType.init(inst, def);
|
|
4416
4434
|
inst._zod.parse = (payload) => payload;
|
|
@@ -5240,6 +5258,9 @@ function handlePipeResult(left, next, ctx) {
|
|
|
5240
5258
|
fallback: left.fallback
|
|
5241
5259
|
}, ctx);
|
|
5242
5260
|
}
|
|
5261
|
+
const $ZodPreprocess = /*@__PURE__*/ $constructor("$ZodPreprocess", (inst, def) => {
|
|
5262
|
+
$ZodPipe.init(inst, def);
|
|
5263
|
+
});
|
|
5243
5264
|
const $ZodReadonly = /*@__PURE__*/ $constructor("$ZodReadonly", (inst, def) => {
|
|
5244
5265
|
$ZodType.init(inst, def);
|
|
5245
5266
|
defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
|
|
@@ -5633,6 +5654,13 @@ function _boolean(Class, params) {
|
|
|
5633
5654
|
});
|
|
5634
5655
|
}
|
|
5635
5656
|
// @__NO_SIDE_EFFECTS__
|
|
5657
|
+
function _null$1(Class, params) {
|
|
5658
|
+
return new Class({
|
|
5659
|
+
type: "null",
|
|
5660
|
+
...normalizeParams(params)
|
|
5661
|
+
});
|
|
5662
|
+
}
|
|
5663
|
+
// @__NO_SIDE_EFFECTS__
|
|
5636
5664
|
function _unknown(Class) {
|
|
5637
5665
|
return new Class({ type: "unknown" });
|
|
5638
5666
|
}
|
|
@@ -5799,6 +5827,17 @@ function _array(Class, element, params) {
|
|
|
5799
5827
|
});
|
|
5800
5828
|
}
|
|
5801
5829
|
// @__NO_SIDE_EFFECTS__
|
|
5830
|
+
function _custom(Class, fn, _params) {
|
|
5831
|
+
const norm = normalizeParams(_params);
|
|
5832
|
+
norm.abort ?? (norm.abort = true);
|
|
5833
|
+
return new Class({
|
|
5834
|
+
type: "custom",
|
|
5835
|
+
check: "custom",
|
|
5836
|
+
fn,
|
|
5837
|
+
...norm
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5840
|
+
// @__NO_SIDE_EFFECTS__
|
|
5802
5841
|
function _refine(Class, fn, _params) {
|
|
5803
5842
|
return new Class({
|
|
5804
5843
|
type: "custom",
|
|
@@ -6181,6 +6220,13 @@ const numberProcessor = (schema, ctx, _json, _params) => {
|
|
|
6181
6220
|
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
6182
6221
|
json.type = "boolean";
|
|
6183
6222
|
};
|
|
6223
|
+
const nullProcessor = (_schema, ctx, json, _params) => {
|
|
6224
|
+
if (ctx.target === "openapi-3.0") {
|
|
6225
|
+
json.type = "string";
|
|
6226
|
+
json.nullable = true;
|
|
6227
|
+
json.enum = [null];
|
|
6228
|
+
} else json.type = "null";
|
|
6229
|
+
};
|
|
6184
6230
|
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
6185
6231
|
json.not = {};
|
|
6186
6232
|
};
|
|
@@ -6874,6 +6920,14 @@ const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => {
|
|
|
6874
6920
|
function boolean(params) {
|
|
6875
6921
|
return /* @__PURE__ */ _boolean(ZodBoolean, params);
|
|
6876
6922
|
}
|
|
6923
|
+
const ZodNull = /*@__PURE__*/ $constructor("ZodNull", (inst, def) => {
|
|
6924
|
+
$ZodNull.init(inst, def);
|
|
6925
|
+
ZodType.init(inst, def);
|
|
6926
|
+
inst._zod.processJSONSchema = (ctx, json, params) => nullProcessor(inst, ctx, json, params);
|
|
6927
|
+
});
|
|
6928
|
+
function _null(params) {
|
|
6929
|
+
return /* @__PURE__ */ _null$1(ZodNull, params);
|
|
6930
|
+
}
|
|
6877
6931
|
const ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => {
|
|
6878
6932
|
$ZodUnknown.init(inst, def);
|
|
6879
6933
|
ZodType.init(inst, def);
|
|
@@ -6987,6 +7041,14 @@ function object(shape, params) {
|
|
|
6987
7041
|
...normalizeParams(params)
|
|
6988
7042
|
});
|
|
6989
7043
|
}
|
|
7044
|
+
function looseObject(shape, params) {
|
|
7045
|
+
return new ZodObject({
|
|
7046
|
+
type: "object",
|
|
7047
|
+
shape,
|
|
7048
|
+
catchall: unknown(),
|
|
7049
|
+
...normalizeParams(params)
|
|
7050
|
+
});
|
|
7051
|
+
}
|
|
6990
7052
|
const ZodUnion = /*@__PURE__*/ $constructor("ZodUnion", (inst, def) => {
|
|
6991
7053
|
$ZodUnion.init(inst, def);
|
|
6992
7054
|
ZodType.init(inst, def);
|
|
@@ -7241,6 +7303,10 @@ function pipe(in_, out) {
|
|
|
7241
7303
|
out
|
|
7242
7304
|
});
|
|
7243
7305
|
}
|
|
7306
|
+
const ZodPreprocess = /*@__PURE__*/ $constructor("ZodPreprocess", (inst, def) => {
|
|
7307
|
+
ZodPipe.init(inst, def);
|
|
7308
|
+
$ZodPreprocess.init(inst, def);
|
|
7309
|
+
});
|
|
7244
7310
|
const ZodReadonly = /*@__PURE__*/ $constructor("ZodReadonly", (inst, def) => {
|
|
7245
7311
|
$ZodReadonly.init(inst, def);
|
|
7246
7312
|
ZodType.init(inst, def);
|
|
@@ -7258,12 +7324,22 @@ const ZodCustom = /*@__PURE__*/ $constructor("ZodCustom", (inst, def) => {
|
|
|
7258
7324
|
ZodType.init(inst, def);
|
|
7259
7325
|
inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);
|
|
7260
7326
|
});
|
|
7327
|
+
function custom(fn, _params) {
|
|
7328
|
+
return /* @__PURE__ */ _custom(ZodCustom, fn ?? (() => true), _params);
|
|
7329
|
+
}
|
|
7261
7330
|
function refine(fn, _params = {}) {
|
|
7262
7331
|
return /* @__PURE__ */ _refine(ZodCustom, fn, _params);
|
|
7263
7332
|
}
|
|
7264
7333
|
function superRefine(fn, params) {
|
|
7265
7334
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
7266
7335
|
}
|
|
7336
|
+
function preprocess(fn, schema) {
|
|
7337
|
+
return new ZodPreprocess({
|
|
7338
|
+
type: "pipe",
|
|
7339
|
+
in: transform(fn),
|
|
7340
|
+
out: schema
|
|
7341
|
+
});
|
|
7342
|
+
}
|
|
7267
7343
|
//#endregion
|
|
7268
7344
|
//#region src/config/config-schema.ts
|
|
7269
7345
|
const RuntimeDefSchema = object({
|
|
@@ -7328,9 +7404,21 @@ function registryCachePath(root) {
|
|
|
7328
7404
|
function logsDir(root) {
|
|
7329
7405
|
return join(root, "logs");
|
|
7330
7406
|
}
|
|
7407
|
+
/**
|
|
7408
|
+
* Unix-domain socket the daemon's MCP control server listens on. The stdio
|
|
7409
|
+
* `agentconnect mcp-bridge` subprocess (spawned by the agent harness) connects
|
|
7410
|
+
* here to forward tool calls back to the daemon. Kept short (macOS caps UDS
|
|
7411
|
+
* paths at ~104 bytes) and under `run/` so it's separate from durable state.
|
|
7412
|
+
*/
|
|
7413
|
+
function mcpSocketPath(root) {
|
|
7414
|
+
return join(root, "run", "mcp.sock");
|
|
7415
|
+
}
|
|
7331
7416
|
function daemonLogPath(root) {
|
|
7332
7417
|
return join(root, "logs", "daemon.log");
|
|
7333
7418
|
}
|
|
7419
|
+
function lockPath(root) {
|
|
7420
|
+
return join(root, "daemon.lock");
|
|
7421
|
+
}
|
|
7334
7422
|
//#endregion
|
|
7335
7423
|
//#region src/config/load-config.ts
|
|
7336
7424
|
function loadConfig(opts = {}) {
|
|
@@ -7567,6 +7655,9 @@ const AgentSchema = object({
|
|
|
7567
7655
|
"paused"
|
|
7568
7656
|
]).default("active"),
|
|
7569
7657
|
runtime: string(),
|
|
7658
|
+
description: string().optional(),
|
|
7659
|
+
reasoningEffort: string().optional(),
|
|
7660
|
+
executionMode: string().optional(),
|
|
7570
7661
|
runtimeOverrides: object({
|
|
7571
7662
|
model: string().optional(),
|
|
7572
7663
|
env: array(object({
|
|
@@ -16786,10 +16877,10 @@ var AcpHost = class {
|
|
|
16786
16877
|
this.canLoad = init.agentCapabilities?.loadSession ?? false;
|
|
16787
16878
|
this.opts.log?.debug(`acp: agent initialized (loadSession capability=${this.canLoad})`);
|
|
16788
16879
|
}
|
|
16789
|
-
async newSession(cwd) {
|
|
16880
|
+
async newSession(cwd, mcpServers = []) {
|
|
16790
16881
|
const res = await this.conn.agent.request(methods.agent.session.new, {
|
|
16791
16882
|
cwd,
|
|
16792
|
-
mcpServers
|
|
16883
|
+
mcpServers
|
|
16793
16884
|
});
|
|
16794
16885
|
this.live.add(res.sessionId);
|
|
16795
16886
|
return res.sessionId;
|
|
@@ -19496,9 +19587,9 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
19496
19587
|
const EventEmitter$3 = __require("events");
|
|
19497
19588
|
const https$3 = __require("https");
|
|
19498
19589
|
const http$7 = __require("http");
|
|
19499
|
-
const net = __require("net");
|
|
19590
|
+
const net$1 = __require("net");
|
|
19500
19591
|
const tls = __require("tls");
|
|
19501
|
-
const { randomBytes, createHash: createHash$1 } = __require("crypto");
|
|
19592
|
+
const { randomBytes: randomBytes$1, createHash: createHash$1 } = __require("crypto");
|
|
19502
19593
|
const { Duplex: Duplex$2, Readable: Readable$1 } = __require("stream");
|
|
19503
19594
|
const { URL: URL$1 } = __require("url");
|
|
19504
19595
|
const PerMessageDeflate = require_permessage_deflate();
|
|
@@ -20065,7 +20156,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
20065
20156
|
}
|
|
20066
20157
|
}
|
|
20067
20158
|
const defaultPort = isSecure ? 443 : 80;
|
|
20068
|
-
const key = randomBytes(16).toString("base64");
|
|
20159
|
+
const key = randomBytes$1(16).toString("base64");
|
|
20069
20160
|
const request = isSecure ? https$3.request : http$7.request;
|
|
20070
20161
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
20071
20162
|
let perMessageDeflate;
|
|
@@ -20242,7 +20333,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
20242
20333
|
*/
|
|
20243
20334
|
function netConnect(options) {
|
|
20244
20335
|
options.path = options.socketPath;
|
|
20245
|
-
return net.connect(options);
|
|
20336
|
+
return net$1.connect(options);
|
|
20246
20337
|
}
|
|
20247
20338
|
/**
|
|
20248
20339
|
* Create a `tls.TLSSocket` and initiate a connection.
|
|
@@ -20253,7 +20344,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
20253
20344
|
*/
|
|
20254
20345
|
function tlsConnect(options) {
|
|
20255
20346
|
options.path = void 0;
|
|
20256
|
-
if (!options.servername && options.servername !== "") options.servername = net.isIP(options.host) ? "" : options.host;
|
|
20347
|
+
if (!options.servername && options.servername !== "") options.servername = net$1.isIP(options.host) ? "" : options.host;
|
|
20257
20348
|
return tls.connect(options);
|
|
20258
20349
|
}
|
|
20259
20350
|
/**
|
|
@@ -22985,6 +23076,44 @@ function watch$1(paths, options = {}) {
|
|
|
22985
23076
|
return watcher;
|
|
22986
23077
|
}
|
|
22987
23078
|
//#endregion
|
|
23079
|
+
//#region src/agents/cp-overlay.ts
|
|
23080
|
+
/** Apply a CP `AgentSpec` onto a local agent, returning a new overlaid agent. */
|
|
23081
|
+
function overlayCpSpec(base, spec) {
|
|
23082
|
+
const envMap = new Map((base.runtimeOverrides?.env ?? []).map((e) => [e.name, e.value]));
|
|
23083
|
+
for (const [name, value] of Object.entries(spec.env ?? {})) envMap.set(name, value);
|
|
23084
|
+
const env = [...envMap].map(([name, value]) => ({
|
|
23085
|
+
name,
|
|
23086
|
+
value
|
|
23087
|
+
}));
|
|
23088
|
+
return {
|
|
23089
|
+
...base,
|
|
23090
|
+
name: spec.name,
|
|
23091
|
+
...spec.description !== void 0 ? { description: spec.description } : {},
|
|
23092
|
+
...spec.reasoningEffort !== void 0 ? { reasoningEffort: spec.reasoningEffort } : {},
|
|
23093
|
+
...spec.executionMode !== void 0 ? { executionMode: spec.executionMode } : {},
|
|
23094
|
+
runtimeOverrides: {
|
|
23095
|
+
...base.runtimeOverrides,
|
|
23096
|
+
...spec.model !== void 0 ? { model: spec.model } : {},
|
|
23097
|
+
env
|
|
23098
|
+
}
|
|
23099
|
+
};
|
|
23100
|
+
}
|
|
23101
|
+
/**
|
|
23102
|
+
* Runtime config the daemon can only surface to an ACP child as environment
|
|
23103
|
+
* variables: ACP `session/new`/`initialize` carry no model or system-prompt
|
|
23104
|
+
* field (SDK v1), so model / reasoning / prompt are exposed under `AGENTCONNECT_*`
|
|
23105
|
+
* for runtimes that read them. (`runtimeOverrides.env` is applied separately by
|
|
23106
|
+
* `agentChildEnv`.) Only emits a key when the value is set.
|
|
23107
|
+
*/
|
|
23108
|
+
function cpRuntimeEnv(agent) {
|
|
23109
|
+
const out = {};
|
|
23110
|
+
if (agent.runtimeOverrides?.model) out.AGENTCONNECT_MODEL = agent.runtimeOverrides.model;
|
|
23111
|
+
if (agent.reasoningEffort) out.AGENTCONNECT_REASONING_EFFORT = agent.reasoningEffort;
|
|
23112
|
+
if (agent.executionMode) out.AGENTCONNECT_EXECUTION_MODE = agent.executionMode;
|
|
23113
|
+
if (agent.description) out.AGENTCONNECT_SYSTEM_PROMPT = agent.description;
|
|
23114
|
+
return out;
|
|
23115
|
+
}
|
|
23116
|
+
//#endregion
|
|
22988
23117
|
//#region src/reconciler/reconciler.ts
|
|
22989
23118
|
/** Stable identity-free signature of an agent's effective config. Excludes the
|
|
22990
23119
|
* loader-only `dir`/`env` fields (present on `LoadedAgent`) so the comparison is
|
|
@@ -23042,6 +23171,10 @@ var LocalStore = class {
|
|
|
23042
23171
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
23043
23172
|
routingEpoch INTEGER, assignments TEXT, globalRules TEXT
|
|
23044
23173
|
);
|
|
23174
|
+
CREATE TABLE IF NOT EXISTS cp_agents (
|
|
23175
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
23176
|
+
specs TEXT
|
|
23177
|
+
);
|
|
23045
23178
|
`);
|
|
23046
23179
|
}
|
|
23047
23180
|
getSession(key) {
|
|
@@ -23075,6 +23208,13 @@ var LocalStore = class {
|
|
|
23075
23208
|
globalRules
|
|
23076
23209
|
});
|
|
23077
23210
|
}
|
|
23211
|
+
getCpAgents() {
|
|
23212
|
+
return this.db.prepare("SELECT specs FROM cp_agents WHERE id = 1").get();
|
|
23213
|
+
}
|
|
23214
|
+
setCpAgents(specs) {
|
|
23215
|
+
this.db.prepare(`INSERT INTO cp_agents (id, specs) VALUES (1, @specs)
|
|
23216
|
+
ON CONFLICT(id) DO UPDATE SET specs=excluded.specs`).run({ specs });
|
|
23217
|
+
}
|
|
23078
23218
|
close() {
|
|
23079
23219
|
this.db.close();
|
|
23080
23220
|
}
|
|
@@ -23106,7 +23246,12 @@ var SessionManager = class {
|
|
|
23106
23246
|
const host = await this.deps.hostFor(agentId);
|
|
23107
23247
|
if (!rec || !rec.acpSessionId) {
|
|
23108
23248
|
const cwd = await prepareWorkspace(agent);
|
|
23109
|
-
const
|
|
23249
|
+
const mcpServers = this.deps.mcpServersFor?.({
|
|
23250
|
+
agent,
|
|
23251
|
+
channel: msg.channel,
|
|
23252
|
+
thread
|
|
23253
|
+
}) ?? [];
|
|
23254
|
+
const acpSessionId = await host.newSession(cwd, mcpServers);
|
|
23110
23255
|
rec = {
|
|
23111
23256
|
key,
|
|
23112
23257
|
agentId,
|
|
@@ -23127,7 +23272,12 @@ var SessionManager = class {
|
|
|
23127
23272
|
resumed = true;
|
|
23128
23273
|
} catch {}
|
|
23129
23274
|
if (!resumed) {
|
|
23130
|
-
const
|
|
23275
|
+
const mcpServers = this.deps.mcpServersFor?.({
|
|
23276
|
+
agent,
|
|
23277
|
+
channel: msg.channel,
|
|
23278
|
+
thread
|
|
23279
|
+
}) ?? [];
|
|
23280
|
+
const acpSessionId = await host.newSession(cwd, mcpServers);
|
|
23131
23281
|
rec = {
|
|
23132
23282
|
...rec,
|
|
23133
23283
|
acpSessionId,
|
|
@@ -23140,7 +23290,7 @@ var SessionManager = class {
|
|
|
23140
23290
|
}
|
|
23141
23291
|
const gap = this.deps.store.transcriptSince(msg.channel, thread, rec.lastDeliveredTs);
|
|
23142
23292
|
const blocks = [];
|
|
23143
|
-
const context = gap.slice(0, -1);
|
|
23293
|
+
const context = gap.slice(0, -1).filter((e) => e.sender !== agentId);
|
|
23144
23294
|
if (context.length > 0) {
|
|
23145
23295
|
const ctxText = context.map((e) => `[${e.sender}] ${e.text}`).join("\n");
|
|
23146
23296
|
blocks.push({
|
|
@@ -23167,6 +23317,306 @@ function tsOf(msg) {
|
|
|
23167
23317
|
return parts[parts.length - 1] ?? "0";
|
|
23168
23318
|
}
|
|
23169
23319
|
//#endregion
|
|
23320
|
+
//#region src/mcp/ipc.ts
|
|
23321
|
+
/** Frame a message for the wire: compact JSON + a single trailing newline. */
|
|
23322
|
+
function encodeFrame(msg) {
|
|
23323
|
+
return JSON.stringify(msg) + "\n";
|
|
23324
|
+
}
|
|
23325
|
+
/**
|
|
23326
|
+
* Split a buffer of newline-delimited frames into parsed objects plus the
|
|
23327
|
+
* trailing partial line. Callers keep `rest` and prepend it to the next chunk.
|
|
23328
|
+
*
|
|
23329
|
+
* Per-line tolerant: a malformed (non-JSON) line is skipped, not thrown — so one
|
|
23330
|
+
* bad frame can't crash a stream reader or discard the good frames batched
|
|
23331
|
+
* alongside it in the same chunk. `onError` lets callers log dropped lines.
|
|
23332
|
+
*/
|
|
23333
|
+
function decodeFrames(buf, onError) {
|
|
23334
|
+
const parts = buf.split("\n");
|
|
23335
|
+
const rest = parts.pop() ?? "";
|
|
23336
|
+
const messages = [];
|
|
23337
|
+
for (const line of parts) {
|
|
23338
|
+
const trimmed = line.trim();
|
|
23339
|
+
if (!trimmed) continue;
|
|
23340
|
+
try {
|
|
23341
|
+
messages.push(JSON.parse(trimmed));
|
|
23342
|
+
} catch (err) {
|
|
23343
|
+
onError?.(trimmed, err);
|
|
23344
|
+
}
|
|
23345
|
+
}
|
|
23346
|
+
return {
|
|
23347
|
+
messages,
|
|
23348
|
+
rest
|
|
23349
|
+
};
|
|
23350
|
+
}
|
|
23351
|
+
//#endregion
|
|
23352
|
+
//#region src/mcp/ops.ts
|
|
23353
|
+
/**
|
|
23354
|
+
* Execute one tool call inside the daemon and return a plain result object (the
|
|
23355
|
+
* bridge wraps it into an MCP `CallToolResult`). Throws on bad input or a
|
|
23356
|
+
* missing connection — the caller turns that into an MCP `isError` result.
|
|
23357
|
+
*/
|
|
23358
|
+
async function executeTool(ctx, name, args, deps) {
|
|
23359
|
+
const gw = deps.gatewayFor(ctx.integrationId);
|
|
23360
|
+
if (!gw) throw new Error(`no live Slack connection for integration ${ctx.integrationId}`);
|
|
23361
|
+
switch (name) {
|
|
23362
|
+
case "sendSlackMessage": {
|
|
23363
|
+
const text = requireString(args, "text");
|
|
23364
|
+
const channel = optionalString(args, "channel") ?? ctx.channel;
|
|
23365
|
+
const thread = "thread" in args ? optionalString(args, "thread") || void 0 : ctx.thread;
|
|
23366
|
+
const ts = await gw.postMessage(channel, text, thread) ?? `local-${deps.now()}`;
|
|
23367
|
+
deps.recordOutbound(ctx, channel, thread, text, ts);
|
|
23368
|
+
return {
|
|
23369
|
+
ok: true,
|
|
23370
|
+
channel,
|
|
23371
|
+
thread: thread ?? null,
|
|
23372
|
+
ts
|
|
23373
|
+
};
|
|
23374
|
+
}
|
|
23375
|
+
case "getCurrentChannel": {
|
|
23376
|
+
const info = await gw.getChannelInfo(ctx.channel).catch(() => void 0);
|
|
23377
|
+
return {
|
|
23378
|
+
channel: ctx.channel,
|
|
23379
|
+
thread: ctx.thread,
|
|
23380
|
+
name: info?.name ?? null,
|
|
23381
|
+
isIm: info?.isIm ?? null
|
|
23382
|
+
};
|
|
23383
|
+
}
|
|
23384
|
+
case "listChannelMembers": {
|
|
23385
|
+
const channel = optionalString(args, "channel") ?? ctx.channel;
|
|
23386
|
+
return {
|
|
23387
|
+
channel,
|
|
23388
|
+
members: await gw.listMembers(channel)
|
|
23389
|
+
};
|
|
23390
|
+
}
|
|
23391
|
+
case "listChannels": return { channels: await gw.listChannels() };
|
|
23392
|
+
case "getUserProfile": {
|
|
23393
|
+
const user = requireString(args, "user");
|
|
23394
|
+
return await gw.getUserProfile(user);
|
|
23395
|
+
}
|
|
23396
|
+
default: throw new Error(`unknown tool: ${name}`);
|
|
23397
|
+
}
|
|
23398
|
+
}
|
|
23399
|
+
function requireString(args, key) {
|
|
23400
|
+
const v = args[key];
|
|
23401
|
+
if (typeof v !== "string" || v.length === 0) throw new Error(`missing required string argument: ${key}`);
|
|
23402
|
+
return v;
|
|
23403
|
+
}
|
|
23404
|
+
function optionalString(args, key) {
|
|
23405
|
+
const v = args[key];
|
|
23406
|
+
if (v === void 0 || v === null) return void 0;
|
|
23407
|
+
if (typeof v !== "string") throw new Error(`argument ${key} must be a string`);
|
|
23408
|
+
return v;
|
|
23409
|
+
}
|
|
23410
|
+
//#endregion
|
|
23411
|
+
//#region src/mcp/control-server.ts
|
|
23412
|
+
/**
|
|
23413
|
+
* The daemon-hosted half of MCP. It owns all tool logic, the registry of live
|
|
23414
|
+
* sessions, and the Unix-domain socket the `mcp-bridge` subprocesses connect to.
|
|
23415
|
+
* "The MCP server is the daemon itself" — the bridge is only a stdio↔socket pipe.
|
|
23416
|
+
*/
|
|
23417
|
+
var McpControlServer = class {
|
|
23418
|
+
deps;
|
|
23419
|
+
server;
|
|
23420
|
+
sessions = /* @__PURE__ */ new Map();
|
|
23421
|
+
conns = /* @__PURE__ */ new Set();
|
|
23422
|
+
constructor(deps) {
|
|
23423
|
+
this.deps = deps;
|
|
23424
|
+
}
|
|
23425
|
+
/**
|
|
23426
|
+
* Register a session's tool set and return an opaque token. The token is
|
|
23427
|
+
* embedded in the bridge's env at `session/new`; the bridge presents it on
|
|
23428
|
+
* every IPC request so we can resolve the channel/thread/agent binding.
|
|
23429
|
+
*/
|
|
23430
|
+
register(ctx) {
|
|
23431
|
+
const token = randomBytes(18).toString("base64url");
|
|
23432
|
+
this.sessions.set(token, ctx);
|
|
23433
|
+
return token;
|
|
23434
|
+
}
|
|
23435
|
+
unregister(token) {
|
|
23436
|
+
this.sessions.delete(token);
|
|
23437
|
+
}
|
|
23438
|
+
async start() {
|
|
23439
|
+
if (this.server) return;
|
|
23440
|
+
const path = this.deps.socketPath;
|
|
23441
|
+
const dir = dirname(path);
|
|
23442
|
+
mkdirSync(dir, {
|
|
23443
|
+
recursive: true,
|
|
23444
|
+
mode: 448
|
|
23445
|
+
});
|
|
23446
|
+
try {
|
|
23447
|
+
chmodSync(dir, 448);
|
|
23448
|
+
} catch {}
|
|
23449
|
+
rmSync(path, { force: true });
|
|
23450
|
+
const server = net.createServer((socket) => this.onConnection(socket));
|
|
23451
|
+
this.server = server;
|
|
23452
|
+
await new Promise((resolve, reject) => {
|
|
23453
|
+
const onStartupError = (err) => reject(err);
|
|
23454
|
+
server.once("error", onStartupError);
|
|
23455
|
+
server.listen(path, () => {
|
|
23456
|
+
server.off("error", onStartupError);
|
|
23457
|
+
server.on("error", (err) => this.deps.log?.error(`mcp: control server error: ${err.message}`));
|
|
23458
|
+
resolve();
|
|
23459
|
+
});
|
|
23460
|
+
});
|
|
23461
|
+
try {
|
|
23462
|
+
chmodSync(path, 384);
|
|
23463
|
+
} catch {}
|
|
23464
|
+
this.deps.log?.info(`mcp: control socket listening at ${path}`);
|
|
23465
|
+
}
|
|
23466
|
+
onConnection(socket) {
|
|
23467
|
+
socket.setEncoding("utf8");
|
|
23468
|
+
this.conns.add(socket);
|
|
23469
|
+
let buf = "";
|
|
23470
|
+
socket.on("data", (chunk) => {
|
|
23471
|
+
buf += chunk;
|
|
23472
|
+
const { messages, rest } = decodeFrames(buf, (line) => this.deps.log?.debug(`mcp: dropping malformed frame: ${line.slice(0, 120)}`));
|
|
23473
|
+
buf = rest;
|
|
23474
|
+
for (const req of messages) this.handle(req, socket);
|
|
23475
|
+
});
|
|
23476
|
+
socket.on("error", (err) => this.deps.log?.debug(`mcp: socket error: ${err.message}`));
|
|
23477
|
+
socket.on("close", () => this.conns.delete(socket));
|
|
23478
|
+
}
|
|
23479
|
+
async handle(req, socket) {
|
|
23480
|
+
const reply = (res) => {
|
|
23481
|
+
if (!socket.destroyed) socket.write(encodeFrame(res));
|
|
23482
|
+
};
|
|
23483
|
+
const ctx = this.sessions.get(req.token);
|
|
23484
|
+
if (!ctx) return reply({
|
|
23485
|
+
id: req.id,
|
|
23486
|
+
ok: false,
|
|
23487
|
+
error: "unknown or expired session token"
|
|
23488
|
+
});
|
|
23489
|
+
try {
|
|
23490
|
+
if (req.op === "listTools") {
|
|
23491
|
+
reply({
|
|
23492
|
+
id: req.id,
|
|
23493
|
+
ok: true,
|
|
23494
|
+
result: { tools: ctx.tools }
|
|
23495
|
+
});
|
|
23496
|
+
return;
|
|
23497
|
+
}
|
|
23498
|
+
const result = await executeTool(ctx, req.name, req.args ?? {}, this.deps);
|
|
23499
|
+
reply({
|
|
23500
|
+
id: req.id,
|
|
23501
|
+
ok: true,
|
|
23502
|
+
result
|
|
23503
|
+
});
|
|
23504
|
+
} catch (err) {
|
|
23505
|
+
reply({
|
|
23506
|
+
id: req.id,
|
|
23507
|
+
ok: false,
|
|
23508
|
+
error: err.message
|
|
23509
|
+
});
|
|
23510
|
+
}
|
|
23511
|
+
}
|
|
23512
|
+
async stop() {
|
|
23513
|
+
this.sessions.clear();
|
|
23514
|
+
const server = this.server;
|
|
23515
|
+
if (!server) return;
|
|
23516
|
+
this.server = void 0;
|
|
23517
|
+
for (const s of this.conns) s.destroy();
|
|
23518
|
+
this.conns.clear();
|
|
23519
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
23520
|
+
rmSync(this.deps.socketPath, { force: true });
|
|
23521
|
+
}
|
|
23522
|
+
};
|
|
23523
|
+
//#endregion
|
|
23524
|
+
//#region src/mcp/inject.ts
|
|
23525
|
+
/**
|
|
23526
|
+
* Build the `mcpServers` entry that makes the agent harness spawn our stdio
|
|
23527
|
+
* bridge. We reuse the *current* interpreter + CLI entry (so it works under both
|
|
23528
|
+
* `tsx` in dev and plain `node` in prod) and invoke the hidden `mcp-bridge`
|
|
23529
|
+
* subcommand. The socket path and per-session token travel via env — they reach
|
|
23530
|
+
* only the harness-spawned subprocess, never the model.
|
|
23531
|
+
*/
|
|
23532
|
+
function buildMcpServers(opts) {
|
|
23533
|
+
return [{
|
|
23534
|
+
name: "agentconnect",
|
|
23535
|
+
command: process.execPath,
|
|
23536
|
+
args: [
|
|
23537
|
+
...process.execArgv,
|
|
23538
|
+
opts.cliEntry,
|
|
23539
|
+
"mcp-bridge"
|
|
23540
|
+
],
|
|
23541
|
+
env: [{
|
|
23542
|
+
name: "AC_MCP_ENDPOINT",
|
|
23543
|
+
value: opts.socketPath
|
|
23544
|
+
}, {
|
|
23545
|
+
name: "AC_MCP_TOKEN",
|
|
23546
|
+
value: opts.token
|
|
23547
|
+
}]
|
|
23548
|
+
}];
|
|
23549
|
+
}
|
|
23550
|
+
//#endregion
|
|
23551
|
+
//#region src/mcp/tools.ts
|
|
23552
|
+
const obj = (properties, required = []) => ({
|
|
23553
|
+
type: "object",
|
|
23554
|
+
properties,
|
|
23555
|
+
required,
|
|
23556
|
+
additionalProperties: false
|
|
23557
|
+
});
|
|
23558
|
+
/**
|
|
23559
|
+
* Tools injected when an agent has a Slack integration. `sendSlackMessage` is
|
|
23560
|
+
* the active-messaging tool from the design (token held by the daemon, invisible
|
|
23561
|
+
* to the model); the rest are channel/user read helpers.
|
|
23562
|
+
*/
|
|
23563
|
+
const SLACK_TOOLS = [
|
|
23564
|
+
{
|
|
23565
|
+
name: "sendSlackMessage",
|
|
23566
|
+
description: "Send a message to a Slack channel. Omit `channel` to post in the channel this conversation is happening in. Omit `thread` to reply in the current thread; pass an empty string to post to the channel root instead.",
|
|
23567
|
+
inputSchema: obj({
|
|
23568
|
+
text: {
|
|
23569
|
+
type: "string",
|
|
23570
|
+
description: "Message body, in Slack mrkdwn."
|
|
23571
|
+
},
|
|
23572
|
+
channel: {
|
|
23573
|
+
type: "string",
|
|
23574
|
+
description: "Target channel ID (e.g. C0123ABC). Defaults to the current channel."
|
|
23575
|
+
},
|
|
23576
|
+
thread: {
|
|
23577
|
+
type: "string",
|
|
23578
|
+
description: "Thread timestamp to reply under. Defaults to the current thread; \"\" posts to the channel root."
|
|
23579
|
+
}
|
|
23580
|
+
}, ["text"])
|
|
23581
|
+
},
|
|
23582
|
+
{
|
|
23583
|
+
name: "getCurrentChannel",
|
|
23584
|
+
description: "Return the Slack channel (and thread) this conversation is bound to, including the channel name when available.",
|
|
23585
|
+
inputSchema: obj({})
|
|
23586
|
+
},
|
|
23587
|
+
{
|
|
23588
|
+
name: "listChannelMembers",
|
|
23589
|
+
description: "List the users and bots in a channel (id, name, is_bot). Omit `channel` to list members of the current channel.",
|
|
23590
|
+
inputSchema: obj({ channel: {
|
|
23591
|
+
type: "string",
|
|
23592
|
+
description: "Channel ID. Defaults to the current channel."
|
|
23593
|
+
} })
|
|
23594
|
+
},
|
|
23595
|
+
{
|
|
23596
|
+
name: "listChannels",
|
|
23597
|
+
description: "List the public/private channels the bot is a member of and can post to.",
|
|
23598
|
+
inputSchema: obj({})
|
|
23599
|
+
},
|
|
23600
|
+
{
|
|
23601
|
+
name: "getUserProfile",
|
|
23602
|
+
description: "Look up a Slack user or bot by id, returning their display name, real name, and bot flag.",
|
|
23603
|
+
inputSchema: obj({ user: {
|
|
23604
|
+
type: "string",
|
|
23605
|
+
description: "User ID (e.g. U0123ABC)."
|
|
23606
|
+
} }, ["user"])
|
|
23607
|
+
}
|
|
23608
|
+
];
|
|
23609
|
+
SLACK_TOOLS.map((t) => t.name);
|
|
23610
|
+
/**
|
|
23611
|
+
* The default MCP tool set for an agent, gated by its integrations. Today only
|
|
23612
|
+
* Slack is supported, so an agent with no Slack integration gets no tools.
|
|
23613
|
+
*/
|
|
23614
|
+
function toolsForIntegrations(integrations) {
|
|
23615
|
+
const tools = [];
|
|
23616
|
+
if (integrations.some((i) => i.platform === "slack")) tools.push(...SLACK_TOOLS);
|
|
23617
|
+
return tools;
|
|
23618
|
+
}
|
|
23619
|
+
//#endregion
|
|
23170
23620
|
//#region src/router/routing-table.ts
|
|
23171
23621
|
const KIND_ORDER = [
|
|
23172
23622
|
"mention",
|
|
@@ -79221,6 +79671,8 @@ function consolidate(agents) {
|
|
|
79221
79671
|
}
|
|
79222
79672
|
return groups;
|
|
79223
79673
|
}
|
|
79674
|
+
/** Cap on members enriched per `listChannelMembers` call (bounds users.info fan-out). */
|
|
79675
|
+
const MEMBER_ENRICH_CAP = 50;
|
|
79224
79676
|
var SlackConnection = class {
|
|
79225
79677
|
deps;
|
|
79226
79678
|
app;
|
|
@@ -79266,11 +79718,51 @@ var SlackConnection = class {
|
|
|
79266
79718
|
log?.debug("slack: app.start resolved → socket established");
|
|
79267
79719
|
}
|
|
79268
79720
|
async postMessage(channel, text, threadTs) {
|
|
79269
|
-
await this.app.client.chat.postMessage({
|
|
79721
|
+
return (await this.app.client.chat.postMessage({
|
|
79270
79722
|
channel,
|
|
79271
79723
|
text,
|
|
79272
79724
|
thread_ts: threadTs
|
|
79273
|
-
});
|
|
79725
|
+
}))?.ts;
|
|
79726
|
+
}
|
|
79727
|
+
async getChannelInfo(channel) {
|
|
79728
|
+
const c = (await this.app.client.conversations.info({ channel })).channel ?? {};
|
|
79729
|
+
return {
|
|
79730
|
+
id: c.id ?? channel,
|
|
79731
|
+
name: c.name,
|
|
79732
|
+
isIm: c.is_im,
|
|
79733
|
+
isPrivate: c.is_private
|
|
79734
|
+
};
|
|
79735
|
+
}
|
|
79736
|
+
async listMembers(channel) {
|
|
79737
|
+
const ids = ((await this.app.client.conversations.members({
|
|
79738
|
+
channel,
|
|
79739
|
+
limit: 200
|
|
79740
|
+
})).members ?? []).slice(0, MEMBER_ENRICH_CAP);
|
|
79741
|
+
return Promise.all(ids.map((id) => this.getUserProfile(id).then((p) => ({
|
|
79742
|
+
id: p.id,
|
|
79743
|
+
name: p.name,
|
|
79744
|
+
isBot: p.isBot
|
|
79745
|
+
})).catch(() => ({ id }))));
|
|
79746
|
+
}
|
|
79747
|
+
async listChannels() {
|
|
79748
|
+
return ((await this.app.client.conversations.list({
|
|
79749
|
+
types: "public_channel,private_channel",
|
|
79750
|
+
exclude_archived: true,
|
|
79751
|
+
limit: 200
|
|
79752
|
+
})).channels ?? []).map((c) => ({
|
|
79753
|
+
id: c.id ?? "",
|
|
79754
|
+
name: c.name,
|
|
79755
|
+
isPrivate: c.is_private
|
|
79756
|
+
}));
|
|
79757
|
+
}
|
|
79758
|
+
async getUserProfile(user) {
|
|
79759
|
+
const u = (await this.app.client.users.info({ user })).user ?? {};
|
|
79760
|
+
return {
|
|
79761
|
+
id: u.id ?? user,
|
|
79762
|
+
name: u.name,
|
|
79763
|
+
realName: u.real_name ?? u.profile?.real_name,
|
|
79764
|
+
isBot: u.is_bot
|
|
79765
|
+
};
|
|
79274
79766
|
}
|
|
79275
79767
|
/**
|
|
79276
79768
|
* Best-effort assistant loading status (assistant.threads.setStatus).
|
|
@@ -79295,9 +79787,18 @@ var SlackConnection = class {
|
|
|
79295
79787
|
};
|
|
79296
79788
|
//#endregion
|
|
79297
79789
|
//#region src/slack/render.ts
|
|
79790
|
+
const THINKING = "is thinking…";
|
|
79791
|
+
const MAX_ACTIVITY = 10;
|
|
79792
|
+
const MAX_LABEL = 100;
|
|
79793
|
+
function clampLabel(s) {
|
|
79794
|
+
const t = s.trim();
|
|
79795
|
+
return t.length > MAX_LABEL ? `${t.slice(0, MAX_LABEL - 1)}…` : t;
|
|
79796
|
+
}
|
|
79298
79797
|
var OutputConverger = class {
|
|
79299
79798
|
mode;
|
|
79300
79799
|
buf = "";
|
|
79800
|
+
activity = [];
|
|
79801
|
+
toolTitles = /* @__PURE__ */ new Map();
|
|
79301
79802
|
constructor(mode) {
|
|
79302
79803
|
this.mode = mode;
|
|
79303
79804
|
}
|
|
@@ -79313,6 +79814,31 @@ var OutputConverger = class {
|
|
|
79313
79814
|
text
|
|
79314
79815
|
}];
|
|
79315
79816
|
}
|
|
79817
|
+
/**
|
|
79818
|
+
* Record an activity label and build the loading-status action carrying the rolling
|
|
79819
|
+
* window as `loading_messages`. Consecutive repeats collapse to nothing (returns []),
|
|
79820
|
+
* which throttles streamed thought chunks down to one status update per thinking run.
|
|
79821
|
+
*/
|
|
79822
|
+
pushActivity(raw) {
|
|
79823
|
+
const label = clampLabel(raw);
|
|
79824
|
+
if (this.activity[this.activity.length - 1] === label) return [];
|
|
79825
|
+
this.activity.push(label);
|
|
79826
|
+
if (this.activity.length > MAX_ACTIVITY) this.activity.shift();
|
|
79827
|
+
return [{
|
|
79828
|
+
kind: "set-status",
|
|
79829
|
+
text: label,
|
|
79830
|
+
loadingMessages: [...this.activity]
|
|
79831
|
+
}];
|
|
79832
|
+
}
|
|
79833
|
+
/** Resolve a tool call's display label, reusing a known title when an update omits it. */
|
|
79834
|
+
toolLabel(update) {
|
|
79835
|
+
const id = update.toolCallId;
|
|
79836
|
+
if (update.title) {
|
|
79837
|
+
if (id) this.toolTitles.set(id, update.title);
|
|
79838
|
+
return update.title;
|
|
79839
|
+
}
|
|
79840
|
+
return (id && this.toolTitles.get(id)) ?? id ?? "tool";
|
|
79841
|
+
}
|
|
79316
79842
|
onUpdate(update) {
|
|
79317
79843
|
switch (update.sessionUpdate) {
|
|
79318
79844
|
case "agent_message_chunk": {
|
|
@@ -79321,42 +79847,53 @@ var OutputConverger = class {
|
|
|
79321
79847
|
return [];
|
|
79322
79848
|
}
|
|
79323
79849
|
case "agent_thought_chunk": {
|
|
79324
|
-
|
|
79325
|
-
|
|
79326
|
-
|
|
79327
|
-
|
|
79328
|
-
|
|
79329
|
-
|
|
79330
|
-
|
|
79331
|
-
|
|
79332
|
-
|
|
79333
|
-
|
|
79850
|
+
const status = this.pushActivity(THINKING);
|
|
79851
|
+
if (this.mode === "high") {
|
|
79852
|
+
const content = update.content;
|
|
79853
|
+
return [
|
|
79854
|
+
...this.flush(),
|
|
79855
|
+
...status,
|
|
79856
|
+
{
|
|
79857
|
+
kind: "update-main",
|
|
79858
|
+
text: `_thinking: ${content?.text ?? ""}_`
|
|
79859
|
+
}
|
|
79860
|
+
];
|
|
79861
|
+
}
|
|
79862
|
+
if (this.mode === "low") return [...this.flush(), ...status];
|
|
79863
|
+
return status;
|
|
79334
79864
|
}
|
|
79335
79865
|
case "tool_call":
|
|
79336
79866
|
case "tool_call_update": {
|
|
79337
|
-
const
|
|
79338
|
-
|
|
79339
|
-
|
|
79340
|
-
|
|
79341
|
-
|
|
79342
|
-
|
|
79343
|
-
|
|
79344
|
-
|
|
79345
|
-
|
|
79867
|
+
const label = this.toolLabel(update);
|
|
79868
|
+
const status = this.pushActivity(label);
|
|
79869
|
+
if (this.mode === "low") return [...this.flush(), ...status];
|
|
79870
|
+
return [
|
|
79871
|
+
...this.flush(),
|
|
79872
|
+
...status,
|
|
79873
|
+
{
|
|
79874
|
+
kind: "update-main",
|
|
79875
|
+
text: `:hammer_and_wrench: ${label}`
|
|
79876
|
+
}
|
|
79877
|
+
];
|
|
79346
79878
|
}
|
|
79347
79879
|
case "usage_update": return [];
|
|
79348
79880
|
default: return [];
|
|
79349
79881
|
}
|
|
79350
79882
|
}
|
|
79351
79883
|
onFinal(link) {
|
|
79352
|
-
|
|
79884
|
+
const clear = {
|
|
79353
79885
|
kind: "set-status",
|
|
79354
79886
|
text: ""
|
|
79355
|
-
}
|
|
79356
|
-
return [...this.flush(),
|
|
79357
|
-
|
|
79358
|
-
|
|
79359
|
-
|
|
79887
|
+
};
|
|
79888
|
+
if (this.mode === "low") return [...this.flush(), clear];
|
|
79889
|
+
return [
|
|
79890
|
+
...this.flush(),
|
|
79891
|
+
clear,
|
|
79892
|
+
{
|
|
79893
|
+
kind: "post",
|
|
79894
|
+
text: `:white_check_mark: done — <${link}|details>`
|
|
79895
|
+
}
|
|
79896
|
+
];
|
|
79360
79897
|
}
|
|
79361
79898
|
};
|
|
79362
79899
|
//#endregion
|
|
@@ -80533,6 +81070,14 @@ var CpClient = class {
|
|
|
80533
81070
|
case "route/update":
|
|
80534
81071
|
this.deps.configApply.applyRouteUpdate(frame.payload);
|
|
80535
81072
|
return;
|
|
81073
|
+
case "agent/upsert": {
|
|
81074
|
+
const u = frame.payload;
|
|
81075
|
+
this.deps.configApply.applyAgentUpsert(u.agentId, u.spec);
|
|
81076
|
+
return;
|
|
81077
|
+
}
|
|
81078
|
+
case "agent/remove":
|
|
81079
|
+
this.deps.configApply.applyAgentRemove(frame.payload.agentId);
|
|
81080
|
+
return;
|
|
80536
81081
|
case "agent/launch":
|
|
80537
81082
|
case "agent/stop":
|
|
80538
81083
|
case "agent/prompt":
|
|
@@ -80586,6 +81131,46 @@ var CpCronRegistry = class {
|
|
|
80586
81131
|
}
|
|
80587
81132
|
};
|
|
80588
81133
|
//#endregion
|
|
81134
|
+
//#region src/cp/cp-agent-registry.ts
|
|
81135
|
+
var CpAgentRegistry = class {
|
|
81136
|
+
io;
|
|
81137
|
+
onChange;
|
|
81138
|
+
specs = /* @__PURE__ */ new Map();
|
|
81139
|
+
constructor(io, onChange) {
|
|
81140
|
+
this.io = io;
|
|
81141
|
+
this.onChange = onChange;
|
|
81142
|
+
const s = io.load();
|
|
81143
|
+
if (s) this.specs = new Map(Object.entries(s));
|
|
81144
|
+
}
|
|
81145
|
+
/** Add or replace one agent's spec (agent/upsert EVT). */
|
|
81146
|
+
upsert(agentId, spec) {
|
|
81147
|
+
this.specs.set(agentId, spec);
|
|
81148
|
+
this.changed();
|
|
81149
|
+
}
|
|
81150
|
+
/** Drop one agent's spec (agent/remove EVT). No-op if absent. */
|
|
81151
|
+
remove(agentId) {
|
|
81152
|
+
if (this.specs.delete(agentId)) this.changed();
|
|
81153
|
+
}
|
|
81154
|
+
/** Make the live set exactly `roster` (register/ok reconcile snapshot). */
|
|
81155
|
+
converge(roster) {
|
|
81156
|
+
const next = /* @__PURE__ */ new Map();
|
|
81157
|
+
for (const { agentId, ...spec } of roster) next.set(agentId, spec);
|
|
81158
|
+
this.specs = next;
|
|
81159
|
+
this.changed();
|
|
81160
|
+
}
|
|
81161
|
+
get(agentId) {
|
|
81162
|
+
return this.specs.get(agentId);
|
|
81163
|
+
}
|
|
81164
|
+
/** agentIds the CP currently wants present. */
|
|
81165
|
+
ids() {
|
|
81166
|
+
return [...this.specs.keys()];
|
|
81167
|
+
}
|
|
81168
|
+
changed() {
|
|
81169
|
+
this.io.save(Object.fromEntries(this.specs));
|
|
81170
|
+
this.onChange();
|
|
81171
|
+
}
|
|
81172
|
+
};
|
|
81173
|
+
//#endregion
|
|
80589
81174
|
//#region src/cp/config-apply.ts
|
|
80590
81175
|
const LOG_LEVELS = /* @__PURE__ */ new Set([
|
|
80591
81176
|
"trace",
|
|
@@ -80669,7 +81254,9 @@ const MAX_QUEUED_PER_SESSION = 10;
|
|
|
80669
81254
|
var Daemon = class {
|
|
80670
81255
|
opts;
|
|
80671
81256
|
store;
|
|
81257
|
+
mcp;
|
|
80672
81258
|
agents = /* @__PURE__ */ new Map();
|
|
81259
|
+
fileAgents = /* @__PURE__ */ new Map();
|
|
80673
81260
|
hosts = /* @__PURE__ */ new Map();
|
|
80674
81261
|
connections = [];
|
|
80675
81262
|
scheduler;
|
|
@@ -80686,6 +81273,7 @@ var Daemon = class {
|
|
|
80686
81273
|
runtimeNames = {};
|
|
80687
81274
|
cpClient;
|
|
80688
81275
|
cpCrons;
|
|
81276
|
+
cpAgents;
|
|
80689
81277
|
botUserIds = {};
|
|
80690
81278
|
cpRouting;
|
|
80691
81279
|
constructor(opts = {}) {
|
|
@@ -80710,7 +81298,7 @@ var Daemon = class {
|
|
|
80710
81298
|
this.log.info(`control plane: ${cfg.controlPlane?.enabled ? `enabled (${cfg.controlPlane.url ?? "no url"})` : "disabled — running local"}`);
|
|
80711
81299
|
this.agentsDir = cfg.agentsDir;
|
|
80712
81300
|
const agents = this.loadAgentList();
|
|
80713
|
-
|
|
81301
|
+
this.fileAgents = new Map(agents.map((a) => [a.id, a]));
|
|
80714
81302
|
this.log.info(`loaded ${agents.length} agent(s) from ${this.agentsDir}${agents.length ? `: ${agents.map((a) => a.id).join(", ")}` : ""}`);
|
|
80715
81303
|
this.root = root;
|
|
80716
81304
|
const resolvedRuntimes = await resolveRuntimes(cfg, root, {
|
|
@@ -80734,10 +81322,50 @@ var Daemon = class {
|
|
|
80734
81322
|
},
|
|
80735
81323
|
save: (s) => this.store.setCpRouting(s.routingEpoch, JSON.stringify(s.assignments), JSON.stringify(s.globalRules))
|
|
80736
81324
|
});
|
|
81325
|
+
this.cpAgents = new CpAgentRegistry({
|
|
81326
|
+
load: () => {
|
|
81327
|
+
const row = this.store.getCpAgents();
|
|
81328
|
+
return row ? JSON.parse(row.specs) : void 0;
|
|
81329
|
+
},
|
|
81330
|
+
save: (s) => this.store.setCpAgents(JSON.stringify(s))
|
|
81331
|
+
}, () => void this.reconcile().catch((err) => this.log.error(`cp: agent reconcile failed: ${err.stack ?? err}`)));
|
|
81332
|
+
for (const a of this.effectiveAgents()) this.agents.set(a.id, a);
|
|
81333
|
+
this.mcp = new McpControlServer({
|
|
81334
|
+
socketPath: mcpSocketPath(root),
|
|
81335
|
+
log: this.log,
|
|
81336
|
+
now: () => Date.now(),
|
|
81337
|
+
gatewayFor: (integrationId) => this.connByIntegration.get(integrationId),
|
|
81338
|
+
recordOutbound: (ctx, channel, thread, text, ts) => this.store.appendTranscript({
|
|
81339
|
+
channel,
|
|
81340
|
+
thread: thread ?? ctx.thread,
|
|
81341
|
+
ts,
|
|
81342
|
+
sender: ctx.agentId,
|
|
81343
|
+
text
|
|
81344
|
+
})
|
|
81345
|
+
});
|
|
81346
|
+
await this.mcp.start();
|
|
81347
|
+
const cliEntry = process.argv[1] ?? "";
|
|
80737
81348
|
this.sessions = new SessionManager({
|
|
80738
81349
|
store: this.store,
|
|
80739
81350
|
hostFor: (agentId) => this.ensureHostAsync(agentId),
|
|
80740
|
-
agentById: (id) => this.agents.get(id)
|
|
81351
|
+
agentById: (id) => this.agents.get(id),
|
|
81352
|
+
mcpServersFor: ({ agent, channel, thread }) => {
|
|
81353
|
+
const tools = toolsForIntegrations(agent.integrations);
|
|
81354
|
+
const integrationId = agent.integrations.find((i) => i.platform === "slack")?.id;
|
|
81355
|
+
if (tools.length === 0 || !integrationId) return [];
|
|
81356
|
+
const token = this.mcp.register({
|
|
81357
|
+
agentId: agent.id,
|
|
81358
|
+
integrationId,
|
|
81359
|
+
channel,
|
|
81360
|
+
thread,
|
|
81361
|
+
tools
|
|
81362
|
+
});
|
|
81363
|
+
return buildMcpServers({
|
|
81364
|
+
socketPath: mcpSocketPath(root),
|
|
81365
|
+
token,
|
|
81366
|
+
cliEntry
|
|
81367
|
+
});
|
|
81368
|
+
}
|
|
80741
81369
|
});
|
|
80742
81370
|
this.scheduler = new Scheduler({
|
|
80743
81371
|
onFire: (agentId, msg) => void this.dispatch(agentId, msg).catch((err) => this.log.error(`cron dispatch failed for agent "${agentId}": ${formatErr(err)}`)),
|
|
@@ -80789,8 +81417,51 @@ var Daemon = class {
|
|
|
80789
81417
|
loadAgentList() {
|
|
80790
81418
|
return this.opts.agentName ? [selectAgent(this.agentsDir, this.opts.agentName)] : loadAgents(this.agentsDir);
|
|
80791
81419
|
}
|
|
81420
|
+
/**
|
|
81421
|
+
* Effective agent set = on-disk `agent.json` base with the CP spec overlaid
|
|
81422
|
+
* (joined by id; CP owns name/description/model/reasoning/execution/env, the
|
|
81423
|
+
* file owns runtime/workspace). CP-only ids with no local base are skipped —
|
|
81424
|
+
* not runnable — and surfaced via cpDegradedScopes().
|
|
81425
|
+
*/
|
|
81426
|
+
effectiveAgents() {
|
|
81427
|
+
return [...this.fileAgents.values()].map((base) => {
|
|
81428
|
+
const spec = this.cpAgents?.get(base.id);
|
|
81429
|
+
return spec ? overlayCpSpec(base, spec) : base;
|
|
81430
|
+
});
|
|
81431
|
+
}
|
|
81432
|
+
/**
|
|
81433
|
+
* Converge the running set to the desired effective agents. Driven by both the
|
|
81434
|
+
* `agents/**` file-watch AND CP convergence (agent/upsert, agent/remove, the
|
|
81435
|
+
* register/ok roster, via the registry's onChange). `diffAgents` detects a
|
|
81436
|
+
* changed effective config (runtime/workspace/model/prompt/env/…) and restarts
|
|
81437
|
+
* the host lazily so the next session picks up fresh config.
|
|
81438
|
+
*
|
|
81439
|
+
* Single-flight: overlapping triggers (e.g. a burst of CP frames, or a file
|
|
81440
|
+
* event landing mid-reconcile) coalesce into one trailing re-run so we never
|
|
81441
|
+
* run two passes concurrently and double-evict a host.
|
|
81442
|
+
*/
|
|
81443
|
+
reconcileRun;
|
|
81444
|
+
reconcilePending = false;
|
|
80792
81445
|
async reconcile() {
|
|
80793
|
-
|
|
81446
|
+
if (this.reconcileRun) {
|
|
81447
|
+
this.reconcilePending = true;
|
|
81448
|
+
return this.reconcileRun;
|
|
81449
|
+
}
|
|
81450
|
+
this.reconcileRun = this.runReconcile();
|
|
81451
|
+
try {
|
|
81452
|
+
await this.reconcileRun;
|
|
81453
|
+
} finally {
|
|
81454
|
+
this.reconcileRun = void 0;
|
|
81455
|
+
}
|
|
81456
|
+
if (this.reconcilePending) {
|
|
81457
|
+
this.reconcilePending = false;
|
|
81458
|
+
await this.reconcile();
|
|
81459
|
+
}
|
|
81460
|
+
}
|
|
81461
|
+
async runReconcile() {
|
|
81462
|
+
const files = this.loadAgentList();
|
|
81463
|
+
this.fileAgents = new Map(files.map((a) => [a.id, a]));
|
|
81464
|
+
const desired = this.effectiveAgents();
|
|
80794
81465
|
const { toStart, toStop, toRestart } = diffAgents(desired, this.agents);
|
|
80795
81466
|
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
81467
|
else this.log.debug(`reconcile: no changes (${desired.length} desired agent(s))`);
|
|
@@ -80825,7 +81496,10 @@ var Daemon = class {
|
|
|
80825
81496
|
if (!runtime) throw new Error(`runtime "${agent.runtime}" not available: not installed on this host, or absent from config.runtimes / the ACP registry`);
|
|
80826
81497
|
host = new AcpHost(runtime, {
|
|
80827
81498
|
onUpdate,
|
|
80828
|
-
env:
|
|
81499
|
+
env: {
|
|
81500
|
+
...agentChildEnv(agent),
|
|
81501
|
+
...cpRuntimeEnv(agent)
|
|
81502
|
+
},
|
|
80829
81503
|
log: this.log
|
|
80830
81504
|
});
|
|
80831
81505
|
}
|
|
@@ -80929,10 +81603,15 @@ var Daemon = class {
|
|
|
80929
81603
|
resolveCpAgent(agentId) {
|
|
80930
81604
|
return resolveAgentIntegration(this.agents.get(agentId), this.botUserIds);
|
|
80931
81605
|
}
|
|
80932
|
-
/**
|
|
81606
|
+
/**
|
|
81607
|
+
* agentIds the daemon can't fully serve: CP routing rules with no servable
|
|
81608
|
+
* Slack integration, plus CP agent specs with no on-disk base (no runtime /
|
|
81609
|
+
* workspace to materialize — the spec alone can't be run).
|
|
81610
|
+
*/
|
|
80933
81611
|
cpDegradedScopes() {
|
|
80934
81612
|
const out = /* @__PURE__ */ new Set();
|
|
80935
81613
|
for (const cpRule of this.cpRouting?.effectiveRules() ?? []) if (!this.resolveCpAgent(cpRule.agentId)) out.add(cpRule.agentId);
|
|
81614
|
+
for (const id of this.cpAgents?.ids() ?? []) if (!this.fileAgents.has(id)) out.add(id);
|
|
80936
81615
|
return [...out];
|
|
80937
81616
|
}
|
|
80938
81617
|
async dispatch(agentId, msg, integrationId) {
|
|
@@ -80958,10 +81637,11 @@ var Daemon = class {
|
|
|
80958
81637
|
}
|
|
80959
81638
|
this.flushQueued(agentId, sessionId, integrationId);
|
|
80960
81639
|
}
|
|
80961
|
-
/** Route a converger action: set-status → setStatus (status
|
|
81640
|
+
/** Route a converger action: set-status → setStatus (status + rotating loading_messages;
|
|
81641
|
+
* '' clears); else postMessage. */
|
|
80962
81642
|
async applyAction(action, conn, channel, thread) {
|
|
80963
81643
|
if (action.kind === "set-status") {
|
|
80964
|
-
if (conn && thread) await conn.setStatus(channel, thread, action.text);
|
|
81644
|
+
if (conn && thread) await conn.setStatus(channel, thread, action.text, action.loadingMessages);
|
|
80965
81645
|
return;
|
|
80966
81646
|
}
|
|
80967
81647
|
await conn?.postMessage(channel, action.text, thread);
|
|
@@ -80996,6 +81676,7 @@ var Daemon = class {
|
|
|
80996
81676
|
},
|
|
80997
81677
|
applyReconcileSnapshot: (snap) => {
|
|
80998
81678
|
this.cpCrons?.converge(snap.crons);
|
|
81679
|
+
this.cpAgents?.converge(snap.agents);
|
|
80999
81680
|
this.cpRouting?.converge({
|
|
81000
81681
|
routingEpoch: snap.routingEpoch,
|
|
81001
81682
|
assignments: snap.assignments,
|
|
@@ -81003,7 +81684,10 @@ var Daemon = class {
|
|
|
81003
81684
|
});
|
|
81004
81685
|
if (snap.leases.length) this.log.debug(`cp: ${snap.leases.length} lease(s) noted (secrets handled later)`);
|
|
81005
81686
|
if (snap.assignments.length) this.log.debug(`cp: converged ${snap.assignments.length} assignment(s)`);
|
|
81687
|
+
if (snap.agents.length) this.log.debug(`cp: converged ${snap.agents.length} agent spec(s)`);
|
|
81006
81688
|
},
|
|
81689
|
+
applyAgentUpsert: (agentId, spec) => this.cpAgents?.upsert(agentId, spec),
|
|
81690
|
+
applyAgentRemove: (agentId) => this.cpAgents?.remove(agentId),
|
|
81007
81691
|
upsertCron: (cron) => this.cpCrons.upsert(cron),
|
|
81008
81692
|
removeCron: (cronId) => this.cpCrons.remove(cronId),
|
|
81009
81693
|
applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),
|
|
@@ -81088,6 +81772,7 @@ var Daemon = class {
|
|
|
81088
81772
|
this.cpCrons?.stop();
|
|
81089
81773
|
for (const c of this.connections) await Promise.resolve(c.stop()).catch((e) => errors.push(e));
|
|
81090
81774
|
for (const h of this.hosts.values()) await Promise.resolve(h.stop()).catch((e) => errors.push(e));
|
|
81775
|
+
await Promise.resolve(this.mcp?.stop()).catch((e) => errors.push(e));
|
|
81091
81776
|
this.store?.close();
|
|
81092
81777
|
if (errors.length) throw new AggregateError(errors, "stop: partial failure");
|
|
81093
81778
|
}
|
|
@@ -81258,12 +81943,78 @@ async function runLogin(opts, partial = {}) {
|
|
|
81258
81943
|
await deps.runForeground();
|
|
81259
81944
|
}
|
|
81260
81945
|
//#endregion
|
|
81946
|
+
//#region src/lock.ts
|
|
81947
|
+
/**
|
|
81948
|
+
* Thrown when a foreground daemon is started while another live daemon already
|
|
81949
|
+
* holds the per-root lock. Two daemons sharing one Slack app token each open a
|
|
81950
|
+
* Socket Mode connection; Slack round-robins events across connections (it does
|
|
81951
|
+
* not broadcast), so each instance receives only a fraction of messages and the
|
|
81952
|
+
* rest appear silently dropped — no error, no log on the instance you're watching.
|
|
81953
|
+
*/
|
|
81954
|
+
var DaemonAlreadyRunningError = class extends Error {
|
|
81955
|
+
pid;
|
|
81956
|
+
lockFile;
|
|
81957
|
+
constructor(pid, lockFile) {
|
|
81958
|
+
super(`another agentconnect daemon is already running (pid ${pid}; lock ${lockFile}). Stop it first — two daemons sharing one Slack app token split Socket Mode events between them, so messages appear to be silently dropped.`);
|
|
81959
|
+
this.pid = pid;
|
|
81960
|
+
this.lockFile = lockFile;
|
|
81961
|
+
this.name = "DaemonAlreadyRunningError";
|
|
81962
|
+
}
|
|
81963
|
+
};
|
|
81964
|
+
/** True if a process with `pid` exists. EPERM ⇒ it exists but is owned by another user. */
|
|
81965
|
+
function pidAlive(pid) {
|
|
81966
|
+
try {
|
|
81967
|
+
process.kill(pid, 0);
|
|
81968
|
+
return true;
|
|
81969
|
+
} catch (err) {
|
|
81970
|
+
return err.code === "EPERM";
|
|
81971
|
+
}
|
|
81972
|
+
}
|
|
81973
|
+
/**
|
|
81974
|
+
* Best-effort single-instance guard for the foreground daemon, keyed by `root`.
|
|
81975
|
+
* A lock file holding a live pid blocks (throws DaemonAlreadyRunningError); a
|
|
81976
|
+
* stale lock (dead pid, garbage, or absent) is reclaimed. `release()` removes the
|
|
81977
|
+
* file only if we still own it. There is a small TOCTOU window — this guards
|
|
81978
|
+
* against accidental double-starts (the launchd service + a manual `run`), not a
|
|
81979
|
+
* determined concurrent race.
|
|
81980
|
+
*
|
|
81981
|
+
* Scoped per-root, not per-app-token: the common collision is two daemons sharing
|
|
81982
|
+
* one `~/.agentconnect`. Distinct roots that happen to carry the same token would
|
|
81983
|
+
* still collide on Slack but not here.
|
|
81984
|
+
*/
|
|
81985
|
+
function acquireSingletonLock(root, deps = {}) {
|
|
81986
|
+
const file = lockPath(root);
|
|
81987
|
+
const myPid = deps.pid ?? process.pid;
|
|
81988
|
+
try {
|
|
81989
|
+
const prev = Number.parseInt(readFileSync(file, "utf8").trim(), 10);
|
|
81990
|
+
if (Number.isInteger(prev) && prev !== myPid && pidAlive(prev)) throw new DaemonAlreadyRunningError(prev, file);
|
|
81991
|
+
} catch (err) {
|
|
81992
|
+
if (err instanceof DaemonAlreadyRunningError) throw err;
|
|
81993
|
+
}
|
|
81994
|
+
writeFileSync(file, `${myPid}\n`, "utf8");
|
|
81995
|
+
let released = false;
|
|
81996
|
+
return { release() {
|
|
81997
|
+
if (released) return;
|
|
81998
|
+
released = true;
|
|
81999
|
+
try {
|
|
82000
|
+
if (Number.parseInt(readFileSync(file, "utf8").trim(), 10) === myPid) unlinkSync(file);
|
|
82001
|
+
} catch {}
|
|
82002
|
+
} };
|
|
82003
|
+
}
|
|
82004
|
+
//#endregion
|
|
81261
82005
|
//#region src/index.ts
|
|
81262
82006
|
const program = new Command();
|
|
81263
82007
|
program.name("agentconnect").description("AgentConnect daemon — edge message + agent execution unit").version(DAEMON_VERSION);
|
|
81264
82008
|
program.option("--config <path>", "path to config.json (default ~/.agentconnect/config.json)").option("--root <dir>", "override ~/.agentconnect root directory").option("--cp-url <url>", "override controlPlane.url").option("--cp-key <key>", "override controlPlane.key (the CP API key)").option("--no-cp", "run fully local, do not connect to the Control Plane").option("--daemon-id <id>", "override daemon identity").option("--log-level <level>", "trace|debug|info|warn|error").option("--agents-dir <dir>", "override agents directory").option("--max-agents <n>", "max agents this daemon advertises / enforces").option("--dry-run", "load + validate config and print the reconcile plan, then exit").option("--agent <name>", "select a single agent by id (run/chat)");
|
|
81265
82009
|
program.command("run").description("Run the daemon in the foreground").action(async () => {
|
|
81266
82010
|
const opts = program.opts();
|
|
82011
|
+
let lock;
|
|
82012
|
+
try {
|
|
82013
|
+
lock = acquireSingletonLock(resolveRoot(opts.root));
|
|
82014
|
+
} catch (err) {
|
|
82015
|
+
console.error(`agentconnect run: ${err.message}`);
|
|
82016
|
+
process.exit(1);
|
|
82017
|
+
}
|
|
81267
82018
|
try {
|
|
81268
82019
|
await runForeground({
|
|
81269
82020
|
root: opts.root,
|
|
@@ -81278,12 +82029,18 @@ program.command("run").description("Run the daemon in the foreground").action(as
|
|
|
81278
82029
|
maxAgents: opts.maxAgents ? Number(opts.maxAgents) : void 0
|
|
81279
82030
|
}
|
|
81280
82031
|
});
|
|
82032
|
+
lock.release();
|
|
81281
82033
|
process.exit(0);
|
|
81282
82034
|
} catch (err) {
|
|
82035
|
+
lock.release();
|
|
81283
82036
|
console.error(`agentconnect run: ${err.message}`);
|
|
81284
82037
|
process.exit(1);
|
|
81285
82038
|
}
|
|
81286
82039
|
});
|
|
82040
|
+
program.command("mcp-bridge", { hidden: true }).description("internal: stdio MCP bridge to the running daemon").action(async () => {
|
|
82041
|
+
const { runBridge } = await import("./bridge-Dj8U1Jp7.js");
|
|
82042
|
+
await runBridge();
|
|
82043
|
+
});
|
|
81287
82044
|
const controller = () => resolveController({ root: program.opts().root });
|
|
81288
82045
|
const requireInstalled = (c) => {
|
|
81289
82046
|
if (!c.isInstalled()) {
|
|
@@ -81415,6 +82172,6 @@ program.command("chat [message]").description("Discover an agent under --agents-
|
|
|
81415
82172
|
});
|
|
81416
82173
|
program.parse();
|
|
81417
82174
|
//#endregion
|
|
81418
|
-
export {};
|
|
82175
|
+
export { __commonJSMin as C, safeParse$1 as S, record as _, _null as a, unknown as b, custom as c, literal as d, looseObject as f, preprocess as g, optional as h, _enum as i, discriminatedUnion as l, object as m, encodeFrame as n, array as o, number as p, DAEMON_VERSION as r, boolean as s, decodeFrames as t, intersection as u, string as v, __toESM as w, datetime as x, union as y };
|
|
81419
82176
|
|
|
81420
82177
|
//# sourceMappingURL=index.js.map
|