@agentconnect.md/daemon 1.0.0-rc.101 → 1.0.0-rc.103
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 +118 -8
- 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"]),
|
|
@@ -81280,7 +81316,8 @@ var SlackConnection = class {
|
|
|
81280
81316
|
id: c.id ?? channel,
|
|
81281
81317
|
name: c.name,
|
|
81282
81318
|
isIm: c.is_im,
|
|
81283
|
-
isPrivate: c.is_private
|
|
81319
|
+
isPrivate: c.is_private,
|
|
81320
|
+
user: c.user
|
|
81284
81321
|
};
|
|
81285
81322
|
}
|
|
81286
81323
|
async listMembers(channel) {
|
|
@@ -81450,6 +81487,11 @@ var SlackNameResolver = class {
|
|
|
81450
81487
|
try {
|
|
81451
81488
|
const info = await conn.getChannelInfo(channel);
|
|
81452
81489
|
if (info.name) this.save(channel, info.name);
|
|
81490
|
+
else if (info.isIm && info.user) {
|
|
81491
|
+
const p = await conn.getUserProfile(info.user);
|
|
81492
|
+
const name = p.realName || p.name;
|
|
81493
|
+
if (name) this.save(channel, `@${name}`);
|
|
81494
|
+
}
|
|
81453
81495
|
} catch (err) {
|
|
81454
81496
|
this.nextAttemptAt.set(channel, this.now() + FAIL_TTL_MS);
|
|
81455
81497
|
this.log?.debug(`slack: name lookup failed for channel ${channel}: ${err.message}`);
|
|
@@ -83008,6 +83050,17 @@ var CpClient = class {
|
|
|
83008
83050
|
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83009
83051
|
this.transport?.send(encode(buildEnvelope("integration/channels", snapshot)));
|
|
83010
83052
|
}
|
|
83053
|
+
/**
|
|
83054
|
+
* Report a CP-owned cron fire (D→C `cron/report` EVT, fire-and-forget) so the
|
|
83055
|
+
* console's `lastRunAt` converges. The local D11 stamp stays authoritative and
|
|
83056
|
+
* the CP upsert is latest-wins, so re-asserting stored stamps on each
|
|
83057
|
+
* (re)connect (see onReady in daemon.ts) makes a dropped report only a delay,
|
|
83058
|
+
* never a loss. No-op unless READY/DRAINING.
|
|
83059
|
+
*/
|
|
83060
|
+
emitCronReport(report) {
|
|
83061
|
+
if (this.state !== "READY" && this.state !== "DRAINING") return;
|
|
83062
|
+
this.transport?.send(encode(buildEnvelope("cron/report", report)));
|
|
83063
|
+
}
|
|
83011
83064
|
request(type, payload) {
|
|
83012
83065
|
const frame = buildEnvelope(type, payload);
|
|
83013
83066
|
return this.correlator.request(frame, (e) => this.transport.send(e));
|
|
@@ -83094,6 +83147,9 @@ var CpClient = class {
|
|
|
83094
83147
|
this.deps.configApply.removeCron(frame.payload.cronId);
|
|
83095
83148
|
this.reply(frame, "ack", { ok: true });
|
|
83096
83149
|
return;
|
|
83150
|
+
case "cron/run":
|
|
83151
|
+
this.reply(frame, "ack", this.deps.configApply.runCron(frame.payload.cronId));
|
|
83152
|
+
return;
|
|
83097
83153
|
case "route/assign": {
|
|
83098
83154
|
const a = frame.payload;
|
|
83099
83155
|
this.deps.configApply.applyRouteAssign(a);
|
|
@@ -83719,7 +83775,8 @@ function toCronDef(cron) {
|
|
|
83719
83775
|
schedule: cron.schedule,
|
|
83720
83776
|
...cron.target && cron.target.platform === "slack" ? { target: {
|
|
83721
83777
|
platform: "slack",
|
|
83722
|
-
channel: cron.target.channel
|
|
83778
|
+
channel: cron.target.channel,
|
|
83779
|
+
...cron.target.integrationId ? { integrationId: cron.target.integrationId } : {}
|
|
83723
83780
|
} } : {},
|
|
83724
83781
|
trigger: cron.trigger,
|
|
83725
83782
|
enabled: cron.enabled !== false,
|
|
@@ -84663,7 +84720,7 @@ var Daemon = class Daemon {
|
|
|
84663
84720
|
async dispatch(agentId, msg, integrationId) {
|
|
84664
84721
|
if (this.draining || this.drainingAgents.has(agentId)) {
|
|
84665
84722
|
this.log.debug(`dispatch: skipped for agent "${agentId}" (draining)`);
|
|
84666
|
-
return;
|
|
84723
|
+
return null;
|
|
84667
84724
|
}
|
|
84668
84725
|
const conv = new OutputConverger(this.agents.get(agentId).output.mode);
|
|
84669
84726
|
const rec = new TranscriptRecorder();
|
|
@@ -84735,6 +84792,7 @@ var Daemon = class Daemon {
|
|
|
84735
84792
|
p.resolveDone();
|
|
84736
84793
|
}
|
|
84737
84794
|
this.flushQueued(agentId, sessionId, integrationId);
|
|
84795
|
+
return sessionId;
|
|
84738
84796
|
}
|
|
84739
84797
|
/** After `!stop` sends session/cancel, give the agent `cancelBackstopMs` to yield.
|
|
84740
84798
|
* If the turn is still in flight when the timer fires, the agent ignored the
|
|
@@ -85086,6 +85144,7 @@ var Daemon = class Daemon {
|
|
|
85086
85144
|
applyIntegrationRemove: (integrationId) => this.cpIntegrations?.remove(integrationId),
|
|
85087
85145
|
upsertCron: (cron) => this.cpCrons.upsert(cron),
|
|
85088
85146
|
removeCron: (cronId) => this.cpCrons.remove(cronId),
|
|
85147
|
+
runCron: (cronId) => this.runCronNow(cronId),
|
|
85089
85148
|
applyRouteAssign: (a) => this.cpRouting?.upsertAssign(a),
|
|
85090
85149
|
applyRouteUpdate: (u) => this.cpRouting?.applyUpdate(u),
|
|
85091
85150
|
applyAgentLaunch: async (launch) => {
|
|
@@ -85119,9 +85178,15 @@ var Daemon = class Daemon {
|
|
|
85119
85178
|
* additionally suppresses all platform output in dispatch.
|
|
85120
85179
|
*/
|
|
85121
85180
|
async onCronFire(agentId, msg, cron) {
|
|
85122
|
-
|
|
85181
|
+
const firedAt = this.clock.now();
|
|
85182
|
+
this.store.setCronLastRun(`${agentId}:${cron.id}`, firedAt);
|
|
85183
|
+
if (cron.origin === "cp") this.cpClient?.emitCronReport({
|
|
85184
|
+
cronId: cron.id,
|
|
85185
|
+
agentId,
|
|
85186
|
+
firedAt: new Date(firedAt).toISOString()
|
|
85187
|
+
});
|
|
85123
85188
|
if (cron.target?.channel) {
|
|
85124
|
-
const conn = this.replyConnFor(agentId);
|
|
85189
|
+
const conn = this.replyConnFor(agentId, cron.target.integrationId);
|
|
85125
85190
|
if (!conn) this.log.warn(`cron "${cron.id}": agent "${agentId}" has no live slack connection — running without output`);
|
|
85126
85191
|
else try {
|
|
85127
85192
|
const ts = await conn.postMessage(cron.target.channel, `⏰ ${cron.trigger}`);
|
|
@@ -85133,7 +85198,43 @@ var Daemon = class Daemon {
|
|
|
85133
85198
|
this.log.warn(`cron "${cron.id}": failed to post trigger to ${cron.target.channel} (${formatErr(err)}) — running without anchor`);
|
|
85134
85199
|
}
|
|
85135
85200
|
}
|
|
85136
|
-
|
|
85201
|
+
try {
|
|
85202
|
+
const sessionId = await this.dispatch(agentId, msg, cron.target?.integrationId);
|
|
85203
|
+
if (cron.origin === "cp" && sessionId !== null) this.cpClient?.emitCronReport({
|
|
85204
|
+
cronId: cron.id,
|
|
85205
|
+
agentId,
|
|
85206
|
+
firedAt: new Date(firedAt).toISOString(),
|
|
85207
|
+
status: "success",
|
|
85208
|
+
durationMs: Math.max(0, this.clock.now() - firedAt),
|
|
85209
|
+
sessionId
|
|
85210
|
+
});
|
|
85211
|
+
} catch (err) {
|
|
85212
|
+
if (cron.origin === "cp") this.cpClient?.emitCronReport({
|
|
85213
|
+
cronId: cron.id,
|
|
85214
|
+
agentId,
|
|
85215
|
+
firedAt: new Date(firedAt).toISOString(),
|
|
85216
|
+
status: "failed",
|
|
85217
|
+
durationMs: Math.max(0, this.clock.now() - firedAt),
|
|
85218
|
+
reason: err.message?.slice(0, 300) || "dispatch failed"
|
|
85219
|
+
});
|
|
85220
|
+
throw err;
|
|
85221
|
+
}
|
|
85222
|
+
}
|
|
85223
|
+
/** Console "Run now" (`cron/run` REQ): fire one CP cron immediately. The fire
|
|
85224
|
+
* runs asynchronously — the ack only says whether the cron exists here; the
|
|
85225
|
+
* outcome arrives as normal `cron/report`s. */
|
|
85226
|
+
runCronNow(cronId) {
|
|
85227
|
+
for (const a of this.effectiveAgents()) {
|
|
85228
|
+
const cron = a.crons.find((c) => c.id === cronId && c.origin === "cp");
|
|
85229
|
+
if (!cron) continue;
|
|
85230
|
+
const { msg } = buildSyntheticMessage(a.id, cron, randomUUID());
|
|
85231
|
+
this.onCronFire(a.id, msg, cron).catch((err) => this.log.error(`cron/run dispatch failed for agent "${a.id}": ${formatErr(err)}`));
|
|
85232
|
+
return { ok: true };
|
|
85233
|
+
}
|
|
85234
|
+
return {
|
|
85235
|
+
ok: false,
|
|
85236
|
+
reason: "unknown cron"
|
|
85237
|
+
};
|
|
85137
85238
|
}
|
|
85138
85239
|
/** Real CPU utilization (0..1) across all cores since the previous call — the busy
|
|
85139
85240
|
* fraction of the time delta. Called once per heartbeat, so it reflects load over the
|
|
@@ -85180,6 +85281,15 @@ var Daemon = class Daemon {
|
|
|
85180
85281
|
integrationId,
|
|
85181
85282
|
channels
|
|
85182
85283
|
});
|
|
85284
|
+
for (const a of this.effectiveAgents()) for (const c of a.crons) {
|
|
85285
|
+
if (c.origin !== "cp") continue;
|
|
85286
|
+
const at = this.store.getCronLastRun(`${a.id}:${c.id}`);
|
|
85287
|
+
if (at !== void 0) this.cpClient?.emitCronReport({
|
|
85288
|
+
cronId: c.id,
|
|
85289
|
+
agentId: a.id,
|
|
85290
|
+
firedAt: new Date(at).toISOString()
|
|
85291
|
+
});
|
|
85292
|
+
}
|
|
85183
85293
|
},
|
|
85184
85294
|
localState: () => ({
|
|
85185
85295
|
assignments: [],
|