@agentconnect.md/daemon 1.0.0-rc.101 → 1.0.0-rc.102
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 +111 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7670,7 +7670,8 @@ const CronDefSchema = object({
|
|
|
7670
7670
|
schedule: string(),
|
|
7671
7671
|
target: object({
|
|
7672
7672
|
platform: literal("slack"),
|
|
7673
|
-
channel: string()
|
|
7673
|
+
channel: string(),
|
|
7674
|
+
integrationId: string().optional()
|
|
7674
7675
|
}).optional(),
|
|
7675
7676
|
trigger: string(),
|
|
7676
7677
|
enabled: boolean().default(true),
|
|
@@ -12935,7 +12936,8 @@ const DrainDone = object({ released: array(SessionKey) });
|
|
|
12935
12936
|
*/
|
|
12936
12937
|
const CronTarget = object({
|
|
12937
12938
|
platform: _enum(["slack", "telegram"]).default("slack"),
|
|
12938
|
-
channel: string()
|
|
12939
|
+
channel: string(),
|
|
12940
|
+
integrationId: string().uuid().optional()
|
|
12939
12941
|
});
|
|
12940
12942
|
const CronUpsert = object({
|
|
12941
12943
|
cronId: string().uuid(),
|
|
@@ -12946,6 +12948,36 @@ const CronUpsert = object({
|
|
|
12946
12948
|
enabled: boolean().default(true)
|
|
12947
12949
|
});
|
|
12948
12950
|
const CronRemove = object({ cronId: string().uuid() });
|
|
12951
|
+
/**
|
|
12952
|
+
* `cron/report` (D→C EVT, fire-and-forget) — one CP-owned cron fired. The
|
|
12953
|
+
* daemon stamps the fire into its local store first (it stays authoritative,
|
|
12954
|
+
* §5.4) and reports it here so the console's `lastRunAt` converges; the CP
|
|
12955
|
+
* upsert is latest-wins, so the daemon re-asserting its stored stamps on
|
|
12956
|
+
* reconnect (fires while the CP was unreachable) is idempotent and can never
|
|
12957
|
+
* regress the value. Hand-authored (no-origin) crons are never reported.
|
|
12958
|
+
*
|
|
12959
|
+
* Two reports per fire, keyed by `(cronId, firedAt)`: the FIRE report (no
|
|
12960
|
+
* `status`) opens the run, and the COMPLETION report (with `status` + outcome
|
|
12961
|
+
* fields) closes it once the dispatched turn ends — feeding the console's
|
|
12962
|
+
* per-schedule run history. A completion without a prior fire report (CP was
|
|
12963
|
+
* down at fire time) still creates the run row.
|
|
12964
|
+
*/
|
|
12965
|
+
const CronRunStatus = _enum(["success", "failed"]);
|
|
12966
|
+
const CronReport = object({
|
|
12967
|
+
cronId: string().uuid(),
|
|
12968
|
+
agentId: string().uuid(),
|
|
12969
|
+
firedAt: string().datetime(),
|
|
12970
|
+
status: CronRunStatus.optional(),
|
|
12971
|
+
durationMs: number().int().nonnegative().optional(),
|
|
12972
|
+
sessionId: string().optional(),
|
|
12973
|
+
reason: string().optional()
|
|
12974
|
+
});
|
|
12975
|
+
/**
|
|
12976
|
+
* `cron/run` (C→D REQ → ack) — fire one CP-owned cron NOW (console "Run now").
|
|
12977
|
+
* The daemon accepts (`ok:true`) and runs the fire asynchronously — outcome
|
|
12978
|
+
* arrives as normal `cron/report`s; `ok:false` when it holds no such cron.
|
|
12979
|
+
*/
|
|
12980
|
+
const CronRunNow = object({ cronId: string().uuid() });
|
|
12949
12981
|
//#endregion
|
|
12950
12982
|
//#region ../protocol/dist/frames/secrets.js
|
|
12951
12983
|
/**
|
|
@@ -13546,6 +13578,8 @@ const FRAME_SCHEMAS = {
|
|
|
13546
13578
|
"drain/done": DrainDone,
|
|
13547
13579
|
"cron/upsert": CronUpsert,
|
|
13548
13580
|
"cron/remove": CronRemove,
|
|
13581
|
+
"cron/report": CronReport,
|
|
13582
|
+
"cron/run": CronRunNow,
|
|
13549
13583
|
"integration/upsert": IntegrationUpsert,
|
|
13550
13584
|
"integration/remove": IntegrationRemove,
|
|
13551
13585
|
"integration/channels": IntegrationChannels,
|
|
@@ -13651,6 +13685,8 @@ discriminatedUnion("type", [
|
|
|
13651
13685
|
frame("drain/done", FRAME_SCHEMAS["drain/done"]),
|
|
13652
13686
|
frame("cron/upsert", FRAME_SCHEMAS["cron/upsert"]),
|
|
13653
13687
|
frame("cron/remove", FRAME_SCHEMAS["cron/remove"]),
|
|
13688
|
+
frame("cron/report", FRAME_SCHEMAS["cron/report"]),
|
|
13689
|
+
frame("cron/run", FRAME_SCHEMAS["cron/run"]),
|
|
13654
13690
|
frame("integration/upsert", FRAME_SCHEMAS["integration/upsert"]),
|
|
13655
13691
|
frame("integration/remove", FRAME_SCHEMAS["integration/remove"]),
|
|
13656
13692
|
frame("integration/channels", FRAME_SCHEMAS["integration/channels"]),
|
|
@@ -83008,6 +83044,17 @@ var CpClient = class {
|
|
|
83008
83044
|
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83009
83045
|
this.transport?.send(encode(buildEnvelope("integration/channels", snapshot)));
|
|
83010
83046
|
}
|
|
83047
|
+
/**
|
|
83048
|
+
* Report a CP-owned cron fire (D→C `cron/report` EVT, fire-and-forget) so the
|
|
83049
|
+
* console's `lastRunAt` converges. The local D11 stamp stays authoritative and
|
|
83050
|
+
* the CP upsert is latest-wins, so re-asserting stored stamps on each
|
|
83051
|
+
* (re)connect (see onReady in daemon.ts) makes a dropped report only a delay,
|
|
83052
|
+
* never a loss. No-op unless READY/DRAINING.
|
|
83053
|
+
*/
|
|
83054
|
+
emitCronReport(report) {
|
|
83055
|
+
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83056
|
+
this.transport?.send(encode(buildEnvelope("cron/report", report)));
|
|
83057
|
+
}
|
|
83011
83058
|
request(type, payload) {
|
|
83012
83059
|
const frame = buildEnvelope(type, payload);
|
|
83013
83060
|
return this.correlator.request(frame, (e) => this.transport.send(e));
|
|
@@ -83094,6 +83141,9 @@ var CpClient = class {
|
|
|
83094
83141
|
this.deps.configApply.removeCron(frame.payload.cronId);
|
|
83095
83142
|
this.reply(frame, "ack", { ok: true });
|
|
83096
83143
|
return;
|
|
83144
|
+
case "cron/run":
|
|
83145
|
+
this.reply(frame, "ack", this.deps.configApply.runCron(frame.payload.cronId));
|
|
83146
|
+
return;
|
|
83097
83147
|
case "route/assign": {
|
|
83098
83148
|
const a = frame.payload;
|
|
83099
83149
|
this.deps.configApply.applyRouteAssign(a);
|
|
@@ -83719,7 +83769,8 @@ function toCronDef(cron) {
|
|
|
83719
83769
|
schedule: cron.schedule,
|
|
83720
83770
|
...cron.target && cron.target.platform === "slack" ? { target: {
|
|
83721
83771
|
platform: "slack",
|
|
83722
|
-
channel: cron.target.channel
|
|
83772
|
+
channel: cron.target.channel,
|
|
83773
|
+
...cron.target.integrationId ? { integrationId: cron.target.integrationId } : {}
|
|
83723
83774
|
} } : {},
|
|
83724
83775
|
trigger: cron.trigger,
|
|
83725
83776
|
enabled: cron.enabled !== false,
|
|
@@ -84663,7 +84714,7 @@ var Daemon = class Daemon {
|
|
|
84663
84714
|
async dispatch(agentId, msg, integrationId) {
|
|
84664
84715
|
if (this.draining || this.drainingAgents.has(agentId)) {
|
|
84665
84716
|
this.log.debug(`dispatch: skipped for agent "${agentId}" (draining)`);
|
|
84666
|
-
return;
|
|
84717
|
+
return null;
|
|
84667
84718
|
}
|
|
84668
84719
|
const conv = new OutputConverger(this.agents.get(agentId).output.mode);
|
|
84669
84720
|
const rec = new TranscriptRecorder();
|
|
@@ -84735,6 +84786,7 @@ var Daemon = class Daemon {
|
|
|
84735
84786
|
p.resolveDone();
|
|
84736
84787
|
}
|
|
84737
84788
|
this.flushQueued(agentId, sessionId, integrationId);
|
|
84789
|
+
return sessionId;
|
|
84738
84790
|
}
|
|
84739
84791
|
/** After `!stop` sends session/cancel, give the agent `cancelBackstopMs` to yield.
|
|
84740
84792
|
* If the turn is still in flight when the timer fires, the agent ignored the
|
|
@@ -85086,6 +85138,7 @@ var Daemon = class Daemon {
|
|
|
85086
85138
|
applyIntegrationRemove: (integrationId) => this.cpIntegrations?.remove(integrationId),
|
|
85087
85139
|
upsertCron: (cron) => this.cpCrons.upsert(cron),
|
|
85088
85140
|
removeCron: (cronId) => this.cpCrons.remove(cronId),
|
|
85141
|
+
runCron: (cronId) => this.runCronNow(cronId),
|
|
85089
85142
|
applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),
|
|
85090
85143
|
applyRouteUpdate: (u) => this.cpRouting?.applyUpdate(u),
|
|
85091
85144
|
applyAgentLaunch: async (launch) => {
|
|
@@ -85119,9 +85172,15 @@ var Daemon = class Daemon {
|
|
|
85119
85172
|
* additionally suppresses all platform output in dispatch.
|
|
85120
85173
|
*/
|
|
85121
85174
|
async onCronFire(agentId, msg, cron) {
|
|
85122
|
-
|
|
85175
|
+
const firedAt = this.clock.now();
|
|
85176
|
+
this.store.setCronLastRun(`${agentId}:${cron.id}`, firedAt);
|
|
85177
|
+
if (cron.origin === "cp") this.cpClient?.emitCronReport({
|
|
85178
|
+
cronId: cron.id,
|
|
85179
|
+
agentId,
|
|
85180
|
+
firedAt: new Date(firedAt).toISOString()
|
|
85181
|
+
});
|
|
85123
85182
|
if (cron.target?.channel) {
|
|
85124
|
-
const conn = this.replyConnFor(agentId);
|
|
85183
|
+
const conn = this.replyConnFor(agentId, cron.target.integrationId);
|
|
85125
85184
|
if (!conn) this.log.warn(`cron "${cron.id}": agent "${agentId}" has no live slack connection — running without output`);
|
|
85126
85185
|
else try {
|
|
85127
85186
|
const ts = await conn.postMessage(cron.target.channel, `⏰ ${cron.trigger}`);
|
|
@@ -85133,7 +85192,43 @@ var Daemon = class Daemon {
|
|
|
85133
85192
|
this.log.warn(`cron "${cron.id}": failed to post trigger to ${cron.target.channel} (${formatErr(err)}) — running without anchor`);
|
|
85134
85193
|
}
|
|
85135
85194
|
}
|
|
85136
|
-
|
|
85195
|
+
try {
|
|
85196
|
+
const sessionId = await this.dispatch(agentId, msg, cron.target?.integrationId);
|
|
85197
|
+
if (cron.origin === "cp" && sessionId !== null) this.cpClient?.emitCronReport({
|
|
85198
|
+
cronId: cron.id,
|
|
85199
|
+
agentId,
|
|
85200
|
+
firedAt: new Date(firedAt).toISOString(),
|
|
85201
|
+
status: "success",
|
|
85202
|
+
durationMs: Math.max(0, this.clock.now() - firedAt),
|
|
85203
|
+
sessionId
|
|
85204
|
+
});
|
|
85205
|
+
} catch (err) {
|
|
85206
|
+
if (cron.origin === "cp") this.cpClient?.emitCronReport({
|
|
85207
|
+
cronId: cron.id,
|
|
85208
|
+
agentId,
|
|
85209
|
+
firedAt: new Date(firedAt).toISOString(),
|
|
85210
|
+
status: "failed",
|
|
85211
|
+
durationMs: Math.max(0, this.clock.now() - firedAt),
|
|
85212
|
+
reason: err.message?.slice(0, 300) || "dispatch failed"
|
|
85213
|
+
});
|
|
85214
|
+
throw err;
|
|
85215
|
+
}
|
|
85216
|
+
}
|
|
85217
|
+
/** Console "Run now" (`cron/run` REQ): fire one CP cron immediately. The fire
|
|
85218
|
+
* runs asynchronously — the ack only says whether the cron exists here; the
|
|
85219
|
+
* outcome arrives as normal `cron/report`s. */
|
|
85220
|
+
runCronNow(cronId) {
|
|
85221
|
+
for (const a of this.effectiveAgents()) {
|
|
85222
|
+
const cron = a.crons.find((c) => c.id === cronId && c.origin === "cp");
|
|
85223
|
+
if (!cron) continue;
|
|
85224
|
+
const { msg } = buildSyntheticMessage(a.id, cron, randomUUID());
|
|
85225
|
+
this.onCronFire(a.id, msg, cron).catch((err) => this.log.error(`cron/run dispatch failed for agent "${a.id}": ${formatErr(err)}`));
|
|
85226
|
+
return { ok: true };
|
|
85227
|
+
}
|
|
85228
|
+
return {
|
|
85229
|
+
ok: false,
|
|
85230
|
+
reason: "unknown cron"
|
|
85231
|
+
};
|
|
85137
85232
|
}
|
|
85138
85233
|
/** Real CPU utilization (0..1) across all cores since the previous call — the busy
|
|
85139
85234
|
* fraction of the time delta. Called once per heartbeat, so it reflects load over the
|
|
@@ -85180,6 +85275,15 @@ var Daemon = class Daemon {
|
|
|
85180
85275
|
integrationId,
|
|
85181
85276
|
channels
|
|
85182
85277
|
});
|
|
85278
|
+
for (const a of this.effectiveAgents()) for (const c of a.crons) {
|
|
85279
|
+
if (c.origin !== "cp") continue;
|
|
85280
|
+
const at = this.store.getCronLastRun(`${a.id}:${c.id}`);
|
|
85281
|
+
if (at !== void 0) this.cpClient?.emitCronReport({
|
|
85282
|
+
cronId: c.id,
|
|
85283
|
+
agentId: a.id,
|
|
85284
|
+
firedAt: new Date(at).toISOString()
|
|
85285
|
+
});
|
|
85286
|
+
}
|
|
85183
85287
|
},
|
|
85184
85288
|
localState: () => ({
|
|
85185
85289
|
assignments: [],
|