@agentconnect.md/daemon 1.0.0-rc.53 → 1.0.0-rc.55
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 +89 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17551,7 +17551,10 @@ const AgentScopeDenied = object({
|
|
|
17551
17551
|
*
|
|
17552
17552
|
* SECURITY: `integration/upsert` and `RegisterOk.integrations[]` carry PLAINTEXT
|
|
17553
17553
|
* platform tokens (botToken/appToken). These payloads MUST NEVER be logged — no
|
|
17554
|
-
* body dump on decode error, no register/ok snapshot debug dump.
|
|
17554
|
+
* body dump on decode error, no register/ok snapshot debug dump. The daemon
|
|
17555
|
+
* persists them into the owning agent's local `agent.json` (same trust boundary
|
|
17556
|
+
* as hand-authored agents, which already keep tokens there) so integrations
|
|
17557
|
+
* survive a restart with the CP down.
|
|
17555
17558
|
*
|
|
17556
17559
|
* `signingSecret` is intentionally absent: Socket Mode authenticates with the
|
|
17557
17560
|
* app-level token, so the daemon never needs a signing secret.
|
|
@@ -82315,7 +82318,7 @@ var CpCronRegistry = class {
|
|
|
82315
82318
|
* matching `<agentsDir>/<id>/agent.json`:
|
|
82316
82319
|
*
|
|
82317
82320
|
* - if one with that id exists → field-level MERGE the CP-owned keys into it;
|
|
82318
|
-
* - if none exists → CREATE a new `<agentsDir>/<
|
|
82321
|
+
* - if none exists → CREATE a new `<agentsDir>/<name>/agent.json`.
|
|
82319
82322
|
*
|
|
82320
82323
|
* CRITICAL: we operate on the RAW JSON TEXT (readFileSync → JSON.parse → set
|
|
82321
82324
|
* only CP-owned keys → writeFileSync). We never serialize the parsed/interpolated
|
|
@@ -82381,7 +82384,8 @@ function applySpecFields(raw, spec, opts) {
|
|
|
82381
82384
|
* workspace.path, and all locally-owned keys.
|
|
82382
82385
|
*
|
|
82383
82386
|
* CREATE (no file): synthesize a minimal valid agent.json under a fresh
|
|
82384
|
-
* `<agentsDir>/<
|
|
82387
|
+
* `<agentsDir>/<spec.name>/` dir (named by the unique slug) — runtime from
|
|
82388
|
+
* `spec.runtime` (falling back to
|
|
82385
82389
|
* the first known runtime + a warn), workspace translated or defaulted to
|
|
82386
82390
|
* from-scratch, path daemon-generated.
|
|
82387
82391
|
*/
|
|
@@ -82397,7 +82401,7 @@ function writeAgentSpec(agentsDir, agentId, spec, deps) {
|
|
|
82397
82401
|
writeFileSync(existingFile, JSON.stringify(raw, null, 2) + "\n");
|
|
82398
82402
|
return;
|
|
82399
82403
|
}
|
|
82400
|
-
const agentDir = join(agentsDir,
|
|
82404
|
+
const agentDir = join(agentsDir, spec.name);
|
|
82401
82405
|
const file = join(agentDir, "agent.json");
|
|
82402
82406
|
let runtime = spec.runtime;
|
|
82403
82407
|
if (!runtime) {
|
|
@@ -82469,7 +82473,23 @@ var CpAgentRegistry = class {
|
|
|
82469
82473
|
}
|
|
82470
82474
|
};
|
|
82471
82475
|
//#endregion
|
|
82472
|
-
//#region src/
|
|
82476
|
+
//#region src/agents/write-integration.ts
|
|
82477
|
+
/**
|
|
82478
|
+
* Persist CP-owned platform integrations onto the owning agent's on-disk
|
|
82479
|
+
* `agent.json`, which is the SINGLE SOURCE OF TRUTH — so integrations (and their
|
|
82480
|
+
* Slack tokens) survive a daemon restart with the Control Plane down (graceful
|
|
82481
|
+
* degradation: the daemon opens its Socket Mode connections from disk at start).
|
|
82482
|
+
*
|
|
82483
|
+
* Local hand-authored agents already store Slack tokens in `agent.json`
|
|
82484
|
+
* (agent-schema.ts SlackConfigSchema), so this adds no new secret-on-disk
|
|
82485
|
+
* concept. Tokens on disk are the same trust boundary as the daemon process;
|
|
82486
|
+
* they still MUST NEVER be logged.
|
|
82487
|
+
*
|
|
82488
|
+
* CRITICAL: like write-agent.ts, we operate on the RAW JSON TEXT (readFileSync →
|
|
82489
|
+
* JSON.parse → mutate only `integrations[]` → writeFileSync). We never serialize
|
|
82490
|
+
* the parsed/interpolated `LoadedAgent` — that has `${VAR}` already substituted
|
|
82491
|
+
* and `workspace.path` rewritten absolute. Editing the raw file preserves both.
|
|
82492
|
+
*/
|
|
82473
82493
|
/** Map the wire spec to the daemon's `Integration` shape (agent-schema). */
|
|
82474
82494
|
function toIntegration(spec) {
|
|
82475
82495
|
return {
|
|
@@ -82484,42 +82504,82 @@ function toIntegration(spec) {
|
|
|
82484
82504
|
}
|
|
82485
82505
|
};
|
|
82486
82506
|
}
|
|
82507
|
+
/**
|
|
82508
|
+
* Upsert one integration into the owning agent's `agent.json` `integrations[]`
|
|
82509
|
+
* (replace by `integrationId`, else append). The owning agent is located by its
|
|
82510
|
+
* INTERNAL id (any directory layout). Returns false (with a warn) when no agent
|
|
82511
|
+
* with `spec.agentId` exists on disk — an orphan integration is not persisted;
|
|
82512
|
+
* it self-heals when the agent spec arrives and the CP re-sends (register/ok).
|
|
82513
|
+
*/
|
|
82514
|
+
function writeIntegrationSpec(agentsDir, spec, deps) {
|
|
82515
|
+
const file = findAgentFileById(agentsDir, spec.agentId);
|
|
82516
|
+
if (!file) {
|
|
82517
|
+
deps.warn?.(`cp: integration ${spec.integrationId} references missing agent ${spec.agentId} — not persisted (will retry on next push)`);
|
|
82518
|
+
return false;
|
|
82519
|
+
}
|
|
82520
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
82521
|
+
const list = Array.isArray(raw.integrations) ? raw.integrations : [];
|
|
82522
|
+
const next = toIntegration(spec);
|
|
82523
|
+
const idx = list.findIndex((i) => typeof i === "object" && i !== null && i.id === next.id);
|
|
82524
|
+
if (idx >= 0) list[idx] = next;
|
|
82525
|
+
else list.push(next);
|
|
82526
|
+
raw.integrations = list;
|
|
82527
|
+
writeFileSync(file, JSON.stringify(raw, null, 2) + "\n");
|
|
82528
|
+
return true;
|
|
82529
|
+
}
|
|
82530
|
+
/**
|
|
82531
|
+
* Remove one integration (by id) from whichever agent.json holds it. Scans all
|
|
82532
|
+
* agent files since the owning agent is not part of `integration/remove`.
|
|
82533
|
+
* Returns whether an entry was found and removed. Unparseable files are skipped.
|
|
82534
|
+
*/
|
|
82535
|
+
function removeIntegration(agentsDir, integrationId) {
|
|
82536
|
+
for (const file of findAgentFiles(agentsDir)) {
|
|
82537
|
+
let raw;
|
|
82538
|
+
try {
|
|
82539
|
+
raw = JSON.parse(readFileSync(file, "utf8"));
|
|
82540
|
+
} catch {
|
|
82541
|
+
continue;
|
|
82542
|
+
}
|
|
82543
|
+
if (!Array.isArray(raw.integrations)) continue;
|
|
82544
|
+
const list = raw.integrations;
|
|
82545
|
+
const idx = list.findIndex((i) => typeof i === "object" && i !== null && i.id === integrationId);
|
|
82546
|
+
if (idx === -1) continue;
|
|
82547
|
+
list.splice(idx, 1);
|
|
82548
|
+
writeFileSync(file, JSON.stringify(raw, null, 2) + "\n");
|
|
82549
|
+
return true;
|
|
82550
|
+
}
|
|
82551
|
+
return false;
|
|
82552
|
+
}
|
|
82553
|
+
//#endregion
|
|
82554
|
+
//#region src/cp/cp-integration-registry.ts
|
|
82487
82555
|
var CpIntegrationRegistry = class {
|
|
82556
|
+
agentsDir;
|
|
82557
|
+
deps;
|
|
82488
82558
|
onChange;
|
|
82489
|
-
|
|
82490
|
-
|
|
82559
|
+
constructor(agentsDir, deps, onChange) {
|
|
82560
|
+
this.agentsDir = agentsDir;
|
|
82561
|
+
this.deps = deps;
|
|
82491
82562
|
this.onChange = onChange;
|
|
82492
82563
|
}
|
|
82493
|
-
/** Add or replace one
|
|
82564
|
+
/** Add or replace one integration on the owning agent's disk file (integration/upsert EVT). */
|
|
82494
82565
|
upsert(spec) {
|
|
82495
|
-
this.
|
|
82566
|
+
writeIntegrationSpec(this.agentsDir, spec, this.deps);
|
|
82496
82567
|
this.onChange();
|
|
82497
82568
|
}
|
|
82498
|
-
/**
|
|
82569
|
+
/** Splice one integration out of whichever agent.json holds it (integration/remove EVT). */
|
|
82499
82570
|
remove(integrationId) {
|
|
82500
|
-
|
|
82571
|
+
removeIntegration(this.agentsDir, integrationId);
|
|
82572
|
+
this.onChange();
|
|
82501
82573
|
}
|
|
82502
82574
|
/**
|
|
82503
|
-
*
|
|
82504
|
-
*
|
|
82575
|
+
* Apply the register/ok reconcile roster: upsert EACH entry and NOTHING else.
|
|
82576
|
+
* Never prunes integrations absent from the roster (deletion is only via
|
|
82577
|
+
* integration/remove).
|
|
82505
82578
|
*/
|
|
82506
82579
|
converge(specs) {
|
|
82507
|
-
|
|
82580
|
+
for (const spec of specs ?? []) writeIntegrationSpec(this.agentsDir, spec, this.deps);
|
|
82508
82581
|
this.onChange();
|
|
82509
82582
|
}
|
|
82510
|
-
/** CP integrations grouped by owning agentId, as daemon `Integration[]`. */
|
|
82511
|
-
byAgent() {
|
|
82512
|
-
const out = /* @__PURE__ */ new Map();
|
|
82513
|
-
for (const spec of this.byId.values()) {
|
|
82514
|
-
const list = out.get(spec.agentId) ?? [];
|
|
82515
|
-
list.push(toIntegration(spec));
|
|
82516
|
-
out.set(spec.agentId, list);
|
|
82517
|
-
}
|
|
82518
|
-
return out;
|
|
82519
|
-
}
|
|
82520
|
-
get size() {
|
|
82521
|
-
return this.byId.size;
|
|
82522
|
-
}
|
|
82523
82583
|
};
|
|
82524
82584
|
//#endregion
|
|
82525
82585
|
//#region src/cp/config-apply.ts
|
|
@@ -82663,7 +82723,6 @@ var Daemon = class Daemon {
|
|
|
82663
82723
|
cpCrons;
|
|
82664
82724
|
cpAgents;
|
|
82665
82725
|
cpIntegrations;
|
|
82666
|
-
warnedOrphanIntegrations = /* @__PURE__ */ new Set();
|
|
82667
82726
|
botUserIds = {};
|
|
82668
82727
|
cpRouting;
|
|
82669
82728
|
clock;
|
|
@@ -82726,7 +82785,7 @@ var Daemon = class Daemon {
|
|
|
82726
82785
|
knownRuntimes: Object.keys(this.runtimes),
|
|
82727
82786
|
warn: (m) => this.log.warn(m)
|
|
82728
82787
|
}, () => void this.reconcile().catch((err) => this.log.error(`cp: agent reconcile failed: ${err.stack ?? err}`)));
|
|
82729
|
-
this.cpIntegrations = new CpIntegrationRegistry(() => void this.reconcile().catch((err) => this.log.error(`cp: integration reconcile failed: ${err.stack ?? err}`)));
|
|
82788
|
+
this.cpIntegrations = new CpIntegrationRegistry(this.agentsDir, { warn: (m) => this.log.warn(m) }, () => void this.reconcile().catch((err) => this.log.error(`cp: integration reconcile failed: ${err.stack ?? err}`)));
|
|
82730
82789
|
for (const a of this.effectiveAgents()) {
|
|
82731
82790
|
this.agents.set(a.id, a);
|
|
82732
82791
|
this.prefetchClone(a);
|
|
@@ -82834,41 +82893,7 @@ var Daemon = class Daemon {
|
|
|
82834
82893
|
* routing (all of which read `agent.integrations`) see one merged view.
|
|
82835
82894
|
*/
|
|
82836
82895
|
effectiveAgents() {
|
|
82837
|
-
|
|
82838
|
-
const cpByAgent = this.cpIntegrations?.byAgent();
|
|
82839
|
-
if (!cpByAgent || cpByAgent.size === 0) return files;
|
|
82840
|
-
return files.map((a) => {
|
|
82841
|
-
const cp = cpByAgent.get(a.id);
|
|
82842
|
-
if (!cp || cp.length === 0) return a;
|
|
82843
|
-
const byId = new Map(a.integrations.map((i) => [i.id, i]));
|
|
82844
|
-
for (const i of cp) byId.set(i.id, i);
|
|
82845
|
-
return {
|
|
82846
|
-
...a,
|
|
82847
|
-
integrations: [...byId.values()]
|
|
82848
|
-
};
|
|
82849
|
-
});
|
|
82850
|
-
}
|
|
82851
|
-
/**
|
|
82852
|
-
* Warn (once per orphan) about CP integrations whose owning agent is absent /
|
|
82853
|
-
* inactive on disk: they are held in the registry but overlaid onto no agent, so
|
|
82854
|
-
* no socket opens. Self-heals when the agent appears (a later reconcile connects
|
|
82855
|
-
* it). Logs integrationId + agentId ONLY — never the token material.
|
|
82856
|
-
*/
|
|
82857
|
-
warnOrphanIntegrations() {
|
|
82858
|
-
const cpByAgent = this.cpIntegrations?.byAgent();
|
|
82859
|
-
if (!cpByAgent) return;
|
|
82860
|
-
const live = /* @__PURE__ */ new Set();
|
|
82861
|
-
for (const [agentId, ints] of cpByAgent) {
|
|
82862
|
-
if (this.fileAgents.has(agentId)) continue;
|
|
82863
|
-
for (const int of ints) {
|
|
82864
|
-
live.add(int.id);
|
|
82865
|
-
if (!this.warnedOrphanIntegrations.has(int.id)) {
|
|
82866
|
-
this.warnedOrphanIntegrations.add(int.id);
|
|
82867
|
-
this.log.warn(`slack: integration ${int.id} references missing/inactive agent ${agentId} — not connected`);
|
|
82868
|
-
}
|
|
82869
|
-
}
|
|
82870
|
-
}
|
|
82871
|
-
for (const id of this.warnedOrphanIntegrations) if (!live.has(id)) this.warnedOrphanIntegrations.delete(id);
|
|
82896
|
+
return [...this.fileAgents.values()];
|
|
82872
82897
|
}
|
|
82873
82898
|
/**
|
|
82874
82899
|
* Converge the running set to the desired effective agents. Driven by both the
|
|
@@ -82902,7 +82927,6 @@ var Daemon = class Daemon {
|
|
|
82902
82927
|
async runReconcile() {
|
|
82903
82928
|
const files = this.loadAgentList();
|
|
82904
82929
|
this.fileAgents = new Map(files.map((a) => [a.id, a]));
|
|
82905
|
-
this.warnOrphanIntegrations();
|
|
82906
82930
|
const desired = this.effectiveAgents();
|
|
82907
82931
|
const { toStart, toStop, toChange } = diffAgents(desired, this.agents);
|
|
82908
82932
|
if (toStart.length || toStop.length || toChange.length) this.log.info(`reconcile: ${desired.length} desired agent(s) from ${this.agentsDir}; start=[${toStart.map((a) => a.id).join(", ")}] stop=[${toStop.join(", ")}] change=[${toChange.map((c) => {
|