@agentconnect.md/daemon 1.0.0-rc.54 → 1.0.0-rc.56
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 +92 -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.
|
|
@@ -80829,9 +80832,13 @@ function splitIntoSections(text, maxLen = SLACK_SECTION_LIMIT) {
|
|
|
80829
80832
|
const THINKING = "is thinking…";
|
|
80830
80833
|
const MAX_ACTIVITY = 10;
|
|
80831
80834
|
const MAX_LABEL = 100;
|
|
80832
|
-
|
|
80835
|
+
const MAX_STATUS = 50;
|
|
80836
|
+
function clampTo(s, max) {
|
|
80833
80837
|
const t = s.trim();
|
|
80834
|
-
return t.length >
|
|
80838
|
+
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
80839
|
+
}
|
|
80840
|
+
function clampLabel(s) {
|
|
80841
|
+
return clampTo(s, MAX_LABEL);
|
|
80835
80842
|
}
|
|
80836
80843
|
function planIcon(status) {
|
|
80837
80844
|
if (status === "completed") return ":white_check_mark:";
|
|
@@ -80875,7 +80882,7 @@ var OutputConverger = class {
|
|
|
80875
80882
|
* which throttles streamed thought chunks down to one status update per thinking run.
|
|
80876
80883
|
*/
|
|
80877
80884
|
pushActivity(raw) {
|
|
80878
|
-
const label =
|
|
80885
|
+
const label = clampTo(raw, MAX_STATUS);
|
|
80879
80886
|
if (this.activity[this.activity.length - 1] === label) return [];
|
|
80880
80887
|
this.activity.push(label);
|
|
80881
80888
|
if (this.activity.length > MAX_ACTIVITY) this.activity.shift();
|
|
@@ -82470,7 +82477,23 @@ var CpAgentRegistry = class {
|
|
|
82470
82477
|
}
|
|
82471
82478
|
};
|
|
82472
82479
|
//#endregion
|
|
82473
|
-
//#region src/
|
|
82480
|
+
//#region src/agents/write-integration.ts
|
|
82481
|
+
/**
|
|
82482
|
+
* Persist CP-owned platform integrations onto the owning agent's on-disk
|
|
82483
|
+
* `agent.json`, which is the SINGLE SOURCE OF TRUTH — so integrations (and their
|
|
82484
|
+
* Slack tokens) survive a daemon restart with the Control Plane down (graceful
|
|
82485
|
+
* degradation: the daemon opens its Socket Mode connections from disk at start).
|
|
82486
|
+
*
|
|
82487
|
+
* Local hand-authored agents already store Slack tokens in `agent.json`
|
|
82488
|
+
* (agent-schema.ts SlackConfigSchema), so this adds no new secret-on-disk
|
|
82489
|
+
* concept. Tokens on disk are the same trust boundary as the daemon process;
|
|
82490
|
+
* they still MUST NEVER be logged.
|
|
82491
|
+
*
|
|
82492
|
+
* CRITICAL: like write-agent.ts, we operate on the RAW JSON TEXT (readFileSync →
|
|
82493
|
+
* JSON.parse → mutate only `integrations[]` → writeFileSync). We never serialize
|
|
82494
|
+
* the parsed/interpolated `LoadedAgent` — that has `${VAR}` already substituted
|
|
82495
|
+
* and `workspace.path` rewritten absolute. Editing the raw file preserves both.
|
|
82496
|
+
*/
|
|
82474
82497
|
/** Map the wire spec to the daemon's `Integration` shape (agent-schema). */
|
|
82475
82498
|
function toIntegration(spec) {
|
|
82476
82499
|
return {
|
|
@@ -82485,42 +82508,82 @@ function toIntegration(spec) {
|
|
|
82485
82508
|
}
|
|
82486
82509
|
};
|
|
82487
82510
|
}
|
|
82511
|
+
/**
|
|
82512
|
+
* Upsert one integration into the owning agent's `agent.json` `integrations[]`
|
|
82513
|
+
* (replace by `integrationId`, else append). The owning agent is located by its
|
|
82514
|
+
* INTERNAL id (any directory layout). Returns false (with a warn) when no agent
|
|
82515
|
+
* with `spec.agentId` exists on disk — an orphan integration is not persisted;
|
|
82516
|
+
* it self-heals when the agent spec arrives and the CP re-sends (register/ok).
|
|
82517
|
+
*/
|
|
82518
|
+
function writeIntegrationSpec(agentsDir, spec, deps) {
|
|
82519
|
+
const file = findAgentFileById(agentsDir, spec.agentId);
|
|
82520
|
+
if (!file) {
|
|
82521
|
+
deps.warn?.(`cp: integration ${spec.integrationId} references missing agent ${spec.agentId} — not persisted (will retry on next push)`);
|
|
82522
|
+
return false;
|
|
82523
|
+
}
|
|
82524
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
82525
|
+
const list = Array.isArray(raw.integrations) ? raw.integrations : [];
|
|
82526
|
+
const next = toIntegration(spec);
|
|
82527
|
+
const idx = list.findIndex((i) => typeof i === "object" && i !== null && i.id === next.id);
|
|
82528
|
+
if (idx >= 0) list[idx] = next;
|
|
82529
|
+
else list.push(next);
|
|
82530
|
+
raw.integrations = list;
|
|
82531
|
+
writeFileSync(file, JSON.stringify(raw, null, 2) + "\n");
|
|
82532
|
+
return true;
|
|
82533
|
+
}
|
|
82534
|
+
/**
|
|
82535
|
+
* Remove one integration (by id) from whichever agent.json holds it. Scans all
|
|
82536
|
+
* agent files since the owning agent is not part of `integration/remove`.
|
|
82537
|
+
* Returns whether an entry was found and removed. Unparseable files are skipped.
|
|
82538
|
+
*/
|
|
82539
|
+
function removeIntegration(agentsDir, integrationId) {
|
|
82540
|
+
for (const file of findAgentFiles(agentsDir)) {
|
|
82541
|
+
let raw;
|
|
82542
|
+
try {
|
|
82543
|
+
raw = JSON.parse(readFileSync(file, "utf8"));
|
|
82544
|
+
} catch {
|
|
82545
|
+
continue;
|
|
82546
|
+
}
|
|
82547
|
+
if (!Array.isArray(raw.integrations)) continue;
|
|
82548
|
+
const list = raw.integrations;
|
|
82549
|
+
const idx = list.findIndex((i) => typeof i === "object" && i !== null && i.id === integrationId);
|
|
82550
|
+
if (idx === -1) continue;
|
|
82551
|
+
list.splice(idx, 1);
|
|
82552
|
+
writeFileSync(file, JSON.stringify(raw, null, 2) + "\n");
|
|
82553
|
+
return true;
|
|
82554
|
+
}
|
|
82555
|
+
return false;
|
|
82556
|
+
}
|
|
82557
|
+
//#endregion
|
|
82558
|
+
//#region src/cp/cp-integration-registry.ts
|
|
82488
82559
|
var CpIntegrationRegistry = class {
|
|
82560
|
+
agentsDir;
|
|
82561
|
+
deps;
|
|
82489
82562
|
onChange;
|
|
82490
|
-
|
|
82491
|
-
|
|
82563
|
+
constructor(agentsDir, deps, onChange) {
|
|
82564
|
+
this.agentsDir = agentsDir;
|
|
82565
|
+
this.deps = deps;
|
|
82492
82566
|
this.onChange = onChange;
|
|
82493
82567
|
}
|
|
82494
|
-
/** Add or replace one
|
|
82568
|
+
/** Add or replace one integration on the owning agent's disk file (integration/upsert EVT). */
|
|
82495
82569
|
upsert(spec) {
|
|
82496
|
-
this.
|
|
82570
|
+
writeIntegrationSpec(this.agentsDir, spec, this.deps);
|
|
82497
82571
|
this.onChange();
|
|
82498
82572
|
}
|
|
82499
|
-
/**
|
|
82573
|
+
/** Splice one integration out of whichever agent.json holds it (integration/remove EVT). */
|
|
82500
82574
|
remove(integrationId) {
|
|
82501
|
-
|
|
82575
|
+
removeIntegration(this.agentsDir, integrationId);
|
|
82576
|
+
this.onChange();
|
|
82502
82577
|
}
|
|
82503
82578
|
/**
|
|
82504
|
-
*
|
|
82505
|
-
*
|
|
82579
|
+
* Apply the register/ok reconcile roster: upsert EACH entry and NOTHING else.
|
|
82580
|
+
* Never prunes integrations absent from the roster (deletion is only via
|
|
82581
|
+
* integration/remove).
|
|
82506
82582
|
*/
|
|
82507
82583
|
converge(specs) {
|
|
82508
|
-
|
|
82584
|
+
for (const spec of specs ?? []) writeIntegrationSpec(this.agentsDir, spec, this.deps);
|
|
82509
82585
|
this.onChange();
|
|
82510
82586
|
}
|
|
82511
|
-
/** CP integrations grouped by owning agentId, as daemon `Integration[]`. */
|
|
82512
|
-
byAgent() {
|
|
82513
|
-
const out = /* @__PURE__ */ new Map();
|
|
82514
|
-
for (const spec of this.byId.values()) {
|
|
82515
|
-
const list = out.get(spec.agentId) ?? [];
|
|
82516
|
-
list.push(toIntegration(spec));
|
|
82517
|
-
out.set(spec.agentId, list);
|
|
82518
|
-
}
|
|
82519
|
-
return out;
|
|
82520
|
-
}
|
|
82521
|
-
get size() {
|
|
82522
|
-
return this.byId.size;
|
|
82523
|
-
}
|
|
82524
82587
|
};
|
|
82525
82588
|
//#endregion
|
|
82526
82589
|
//#region src/cp/config-apply.ts
|
|
@@ -82664,7 +82727,6 @@ var Daemon = class Daemon {
|
|
|
82664
82727
|
cpCrons;
|
|
82665
82728
|
cpAgents;
|
|
82666
82729
|
cpIntegrations;
|
|
82667
|
-
warnedOrphanIntegrations = /* @__PURE__ */ new Set();
|
|
82668
82730
|
botUserIds = {};
|
|
82669
82731
|
cpRouting;
|
|
82670
82732
|
clock;
|
|
@@ -82727,7 +82789,7 @@ var Daemon = class Daemon {
|
|
|
82727
82789
|
knownRuntimes: Object.keys(this.runtimes),
|
|
82728
82790
|
warn: (m) => this.log.warn(m)
|
|
82729
82791
|
}, () => void this.reconcile().catch((err) => this.log.error(`cp: agent reconcile failed: ${err.stack ?? err}`)));
|
|
82730
|
-
this.cpIntegrations = new CpIntegrationRegistry(() => void this.reconcile().catch((err) => this.log.error(`cp: integration reconcile failed: ${err.stack ?? err}`)));
|
|
82792
|
+
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}`)));
|
|
82731
82793
|
for (const a of this.effectiveAgents()) {
|
|
82732
82794
|
this.agents.set(a.id, a);
|
|
82733
82795
|
this.prefetchClone(a);
|
|
@@ -82835,41 +82897,7 @@ var Daemon = class Daemon {
|
|
|
82835
82897
|
* routing (all of which read `agent.integrations`) see one merged view.
|
|
82836
82898
|
*/
|
|
82837
82899
|
effectiveAgents() {
|
|
82838
|
-
|
|
82839
|
-
const cpByAgent = this.cpIntegrations?.byAgent();
|
|
82840
|
-
if (!cpByAgent || cpByAgent.size === 0) return files;
|
|
82841
|
-
return files.map((a) => {
|
|
82842
|
-
const cp = cpByAgent.get(a.id);
|
|
82843
|
-
if (!cp || cp.length === 0) return a;
|
|
82844
|
-
const byId = new Map(a.integrations.map((i) => [i.id, i]));
|
|
82845
|
-
for (const i of cp) byId.set(i.id, i);
|
|
82846
|
-
return {
|
|
82847
|
-
...a,
|
|
82848
|
-
integrations: [...byId.values()]
|
|
82849
|
-
};
|
|
82850
|
-
});
|
|
82851
|
-
}
|
|
82852
|
-
/**
|
|
82853
|
-
* Warn (once per orphan) about CP integrations whose owning agent is absent /
|
|
82854
|
-
* inactive on disk: they are held in the registry but overlaid onto no agent, so
|
|
82855
|
-
* no socket opens. Self-heals when the agent appears (a later reconcile connects
|
|
82856
|
-
* it). Logs integrationId + agentId ONLY — never the token material.
|
|
82857
|
-
*/
|
|
82858
|
-
warnOrphanIntegrations() {
|
|
82859
|
-
const cpByAgent = this.cpIntegrations?.byAgent();
|
|
82860
|
-
if (!cpByAgent) return;
|
|
82861
|
-
const live = /* @__PURE__ */ new Set();
|
|
82862
|
-
for (const [agentId, ints] of cpByAgent) {
|
|
82863
|
-
if (this.fileAgents.has(agentId)) continue;
|
|
82864
|
-
for (const int of ints) {
|
|
82865
|
-
live.add(int.id);
|
|
82866
|
-
if (!this.warnedOrphanIntegrations.has(int.id)) {
|
|
82867
|
-
this.warnedOrphanIntegrations.add(int.id);
|
|
82868
|
-
this.log.warn(`slack: integration ${int.id} references missing/inactive agent ${agentId} — not connected`);
|
|
82869
|
-
}
|
|
82870
|
-
}
|
|
82871
|
-
}
|
|
82872
|
-
for (const id of this.warnedOrphanIntegrations) if (!live.has(id)) this.warnedOrphanIntegrations.delete(id);
|
|
82900
|
+
return [...this.fileAgents.values()];
|
|
82873
82901
|
}
|
|
82874
82902
|
/**
|
|
82875
82903
|
* Converge the running set to the desired effective agents. Driven by both the
|
|
@@ -82903,7 +82931,6 @@ var Daemon = class Daemon {
|
|
|
82903
82931
|
async runReconcile() {
|
|
82904
82932
|
const files = this.loadAgentList();
|
|
82905
82933
|
this.fileAgents = new Map(files.map((a) => [a.id, a]));
|
|
82906
|
-
this.warnOrphanIntegrations();
|
|
82907
82934
|
const desired = this.effectiveAgents();
|
|
82908
82935
|
const { toStart, toStop, toChange } = diffAgents(desired, this.agents);
|
|
82909
82936
|
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) => {
|