@agentconnect.md/daemon 1.0.0-rc.64 → 1.0.0-rc.66
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 +140 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13227,7 +13227,9 @@ const SessionListItem = object({
|
|
|
13227
13227
|
status: string().optional(),
|
|
13228
13228
|
lastActivityAt: string().optional(),
|
|
13229
13229
|
usage: SessionUsage.optional(),
|
|
13230
|
-
triggeredBy: string().optional()
|
|
13230
|
+
triggeredBy: string().optional(),
|
|
13231
|
+
channelName: string().optional(),
|
|
13232
|
+
triggeredByName: string().optional()
|
|
13231
13233
|
});
|
|
13232
13234
|
/** C→D REQ: list the daemon's sessions (optionally just one agent's). */
|
|
13233
13235
|
const SessionListReq = object({ agentId: string().uuid().optional() });
|
|
@@ -13237,6 +13239,7 @@ const SessionListPage = object({ sessions: array(SessionListItem) });
|
|
|
13237
13239
|
const SessionMessage = object({
|
|
13238
13240
|
seq: number().int(),
|
|
13239
13241
|
sender: string(),
|
|
13242
|
+
senderName: string().optional(),
|
|
13240
13243
|
ts: string(),
|
|
13241
13244
|
kind: string(),
|
|
13242
13245
|
text: string()
|
|
@@ -23761,7 +23764,12 @@ var LocalStore = class {
|
|
|
23761
23764
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
23762
23765
|
key TEXT PRIMARY KEY, agentId TEXT, platform TEXT, channel TEXT, thread TEXT,
|
|
23763
23766
|
acpSessionId TEXT, state TEXT, lastDeliveredTs TEXT, updatedAt INTEGER,
|
|
23764
|
-
usage TEXT, muted INTEGER
|
|
23767
|
+
usage TEXT, muted INTEGER, triggeredBy TEXT
|
|
23768
|
+
);
|
|
23769
|
+
-- Platform id → human display name (Slack channel/user names, daemon-resolved
|
|
23770
|
+
-- and cached here so session read-back can label ids without a Slack call).
|
|
23771
|
+
CREATE TABLE IF NOT EXISTS display_names (
|
|
23772
|
+
id TEXT PRIMARY KEY, name TEXT NOT NULL, updatedAt INTEGER
|
|
23765
23773
|
);
|
|
23766
23774
|
CREATE TABLE IF NOT EXISTS transcript (
|
|
23767
23775
|
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -23780,14 +23788,15 @@ var LocalStore = class {
|
|
|
23780
23788
|
`);
|
|
23781
23789
|
this.migrateSessionUsage();
|
|
23782
23790
|
}
|
|
23783
|
-
/** Add the `sessions.usage` (JSON token accounting)
|
|
23784
|
-
* thread mute) columns to a pre-existing DB. CREATE
|
|
23785
|
-
* them for fresh DBs but never alters an existing
|
|
23786
|
-
* No-op once the columns are present. */
|
|
23791
|
+
/** Add the `sessions.usage` (JSON token accounting), `sessions.muted` (`!stop`
|
|
23792
|
+
* thread mute) and `sessions.triggeredBy` columns to a pre-existing DB. CREATE
|
|
23793
|
+
* TABLE IF NOT EXISTS above adds them for fresh DBs but never alters an existing
|
|
23794
|
+
* table, so upgrade in place. No-op once the columns are present. */
|
|
23787
23795
|
migrateSessionUsage() {
|
|
23788
23796
|
const cols = this.db.prepare("PRAGMA table_info(sessions)").all();
|
|
23789
23797
|
if (!cols.some((c) => c.name === "usage")) this.db.exec("ALTER TABLE sessions ADD COLUMN usage TEXT");
|
|
23790
23798
|
if (!cols.some((c) => c.name === "muted")) this.db.exec("ALTER TABLE sessions ADD COLUMN muted INTEGER");
|
|
23799
|
+
if (!cols.some((c) => c.name === "triggeredBy")) this.db.exec("ALTER TABLE sessions ADD COLUMN triggeredBy TEXT");
|
|
23791
23800
|
}
|
|
23792
23801
|
/**
|
|
23793
23802
|
* Pre-`kind` transcript tables keyed on (channel, thread, ts) hold only conversational
|
|
@@ -23837,11 +23846,12 @@ var LocalStore = class {
|
|
|
23837
23846
|
};
|
|
23838
23847
|
}
|
|
23839
23848
|
upsertSession(rec) {
|
|
23840
|
-
this.db.prepare(`INSERT INTO sessions (key, agentId, platform, channel, thread, acpSessionId, state, lastDeliveredTs, updatedAt)
|
|
23841
|
-
VALUES (@key, @agentId, @platform, @channel, @thread, @acpSessionId, @state, @lastDeliveredTs, @updatedAt)
|
|
23849
|
+
this.db.prepare(`INSERT INTO sessions (key, agentId, platform, channel, thread, acpSessionId, state, lastDeliveredTs, updatedAt, triggeredBy)
|
|
23850
|
+
VALUES (@key, @agentId, @platform, @channel, @thread, @acpSessionId, @state, @lastDeliveredTs, @updatedAt, @triggeredBy)
|
|
23842
23851
|
ON CONFLICT(key) DO UPDATE SET
|
|
23843
23852
|
acpSessionId=excluded.acpSessionId, state=excluded.state,
|
|
23844
|
-
lastDeliveredTs=excluded.lastDeliveredTs, updatedAt=excluded.updatedAt
|
|
23853
|
+
lastDeliveredTs=excluded.lastDeliveredTs, updatedAt=excluded.updatedAt,
|
|
23854
|
+
triggeredBy=COALESCE(sessions.triggeredBy, excluded.triggeredBy)`).run({
|
|
23845
23855
|
key: rec.key,
|
|
23846
23856
|
agentId: rec.agentId,
|
|
23847
23857
|
platform: rec.platform,
|
|
@@ -23850,7 +23860,8 @@ var LocalStore = class {
|
|
|
23850
23860
|
acpSessionId: rec.acpSessionId,
|
|
23851
23861
|
state: rec.state,
|
|
23852
23862
|
lastDeliveredTs: rec.lastDeliveredTs,
|
|
23853
|
-
updatedAt: rec.updatedAt
|
|
23863
|
+
updatedAt: rec.updatedAt,
|
|
23864
|
+
triggeredBy: rec.triggeredBy ?? null
|
|
23854
23865
|
});
|
|
23855
23866
|
}
|
|
23856
23867
|
/** Targeted state transition for an existing session (§7.3), stamping `updatedAt`
|
|
@@ -23947,6 +23958,22 @@ var LocalStore = class {
|
|
|
23947
23958
|
activeSessionCountSince(channel, thread, sinceTs) {
|
|
23948
23959
|
return this.db.prepare("SELECT COUNT(*) AS n FROM sessions WHERE channel = ? AND thread = ? AND state != 'closed' AND updatedAt >= ?").get(channel, thread, sinceTs)?.n ?? 0;
|
|
23949
23960
|
}
|
|
23961
|
+
/** Cache a platform id's human display name (channel or user; Slack ids don't
|
|
23962
|
+
* collide across the two). Latest-wins — renames overwrite. */
|
|
23963
|
+
setDisplayName(id, name, updatedAt) {
|
|
23964
|
+
this.db.prepare(`INSERT INTO display_names (id, name, updatedAt) VALUES (?, ?, ?)
|
|
23965
|
+
ON CONFLICT(id) DO UPDATE SET name=excluded.name, updatedAt=excluded.updatedAt`).run(id, name, updatedAt);
|
|
23966
|
+
}
|
|
23967
|
+
/** Display names for a set of platform ids — only the ids that have one.
|
|
23968
|
+
* One batched `IN (…)` query, not a round-trip per id. */
|
|
23969
|
+
getDisplayNames(ids) {
|
|
23970
|
+
const out = /* @__PURE__ */ new Map();
|
|
23971
|
+
const unique = [...new Set(ids)];
|
|
23972
|
+
if (unique.length === 0) return out;
|
|
23973
|
+
const rows = this.db.prepare(`SELECT id, name FROM display_names WHERE id IN (${unique.map(() => "?").join(",")})`).all(...unique);
|
|
23974
|
+
for (const r of rows) if (r.name) out.set(r.id, r.name);
|
|
23975
|
+
return out;
|
|
23976
|
+
}
|
|
23950
23977
|
getCpRouting() {
|
|
23951
23978
|
return this.db.prepare("SELECT routingEpoch, assignments, globalRules FROM cp_routing WHERE id = 1").get();
|
|
23952
23979
|
}
|
|
@@ -24067,7 +24094,8 @@ var SessionManager = class {
|
|
|
24067
24094
|
acpSessionId,
|
|
24068
24095
|
state: "idle",
|
|
24069
24096
|
lastDeliveredTs: null,
|
|
24070
|
-
updatedAt: Date.now()
|
|
24097
|
+
updatedAt: Date.now(),
|
|
24098
|
+
triggeredBy: msg.sender.id
|
|
24071
24099
|
};
|
|
24072
24100
|
this.deps.store.upsertSession(rec);
|
|
24073
24101
|
} else if (host.hasSession?.(rec.acpSessionId) === false) {
|
|
@@ -80959,6 +80987,74 @@ var SlackConnection = class {
|
|
|
80959
80987
|
}
|
|
80960
80988
|
};
|
|
80961
80989
|
//#endregion
|
|
80990
|
+
//#region src/slack/name-resolver.ts
|
|
80991
|
+
/**
|
|
80992
|
+
* Resolves Slack ids (channel "C…", user "U…/W…") to human display names off the
|
|
80993
|
+
* inbound-message path and caches the results into the LocalStore `display_names`
|
|
80994
|
+
* table, so session read-back (`session/list` / `session/history`) can label ids
|
|
80995
|
+
* without ever calling Slack at read time.
|
|
80996
|
+
*
|
|
80997
|
+
* Design constraints:
|
|
80998
|
+
* - Fire-and-forget: never on the dispatch hot path, never throws into it.
|
|
80999
|
+
* - Rate-friendly: an in-memory attempt cache means each id hits the Web API at
|
|
81000
|
+
* most once per TTL (short TTL after a failure, long after success/no-name),
|
|
81001
|
+
* regardless of message volume.
|
|
81002
|
+
* - Names persist in SQLite, so a daemon restart keeps stale-but-useful names
|
|
81003
|
+
* while the cache warms back up (rename lag is bounded by OK_TTL_MS).
|
|
81004
|
+
*/
|
|
81005
|
+
const OK_TTL_MS = 360 * 60 * 1e3;
|
|
81006
|
+
const FAIL_TTL_MS = 600 * 1e3;
|
|
81007
|
+
const MAX_TRACKED_IDS = 5e3;
|
|
81008
|
+
var SlackNameResolver = class {
|
|
81009
|
+
save;
|
|
81010
|
+
log;
|
|
81011
|
+
now;
|
|
81012
|
+
/** id → epoch ms until which we won't re-attempt a lookup. */
|
|
81013
|
+
nextAttemptAt = /* @__PURE__ */ new Map();
|
|
81014
|
+
constructor(save, log, now = Date.now) {
|
|
81015
|
+
this.save = save;
|
|
81016
|
+
this.log = log;
|
|
81017
|
+
this.now = now;
|
|
81018
|
+
}
|
|
81019
|
+
/** Kick off (cached) resolution for a message's channel + human sender. */
|
|
81020
|
+
noteMessage(conn, msg) {
|
|
81021
|
+
this.resolveChannel(conn, msg.channel);
|
|
81022
|
+
if (!msg.sender.isBot && /^[UW]/.test(msg.sender.id)) this.resolveUser(conn, msg.sender.id);
|
|
81023
|
+
}
|
|
81024
|
+
claim(id) {
|
|
81025
|
+
const at = this.nextAttemptAt.get(id);
|
|
81026
|
+
if (at !== void 0 && this.now() < at) return false;
|
|
81027
|
+
this.nextAttemptAt.delete(id);
|
|
81028
|
+
this.nextAttemptAt.set(id, this.now() + OK_TTL_MS);
|
|
81029
|
+
for (const key of this.nextAttemptAt.keys()) {
|
|
81030
|
+
if (this.nextAttemptAt.size <= MAX_TRACKED_IDS) break;
|
|
81031
|
+
this.nextAttemptAt.delete(key);
|
|
81032
|
+
}
|
|
81033
|
+
return true;
|
|
81034
|
+
}
|
|
81035
|
+
async resolveChannel(conn, channel) {
|
|
81036
|
+
if (!this.claim(channel)) return;
|
|
81037
|
+
try {
|
|
81038
|
+
const info = await conn.getChannelInfo(channel);
|
|
81039
|
+
if (info.name) this.save(channel, info.name);
|
|
81040
|
+
} catch (err) {
|
|
81041
|
+
this.nextAttemptAt.set(channel, this.now() + FAIL_TTL_MS);
|
|
81042
|
+
this.log?.debug(`slack: name lookup failed for channel ${channel}: ${err.message}`);
|
|
81043
|
+
}
|
|
81044
|
+
}
|
|
81045
|
+
async resolveUser(conn, user) {
|
|
81046
|
+
if (!this.claim(user)) return;
|
|
81047
|
+
try {
|
|
81048
|
+
const p = await conn.getUserProfile(user);
|
|
81049
|
+
const name = p.realName || p.name;
|
|
81050
|
+
if (name) this.save(user, name);
|
|
81051
|
+
} catch (err) {
|
|
81052
|
+
this.nextAttemptAt.set(user, this.now() + FAIL_TTL_MS);
|
|
81053
|
+
this.log?.debug(`slack: name lookup failed for user ${user}: ${err.message}`);
|
|
81054
|
+
}
|
|
81055
|
+
}
|
|
81056
|
+
};
|
|
81057
|
+
//#endregion
|
|
80962
81058
|
//#region src/slack/formatter.ts
|
|
80963
81059
|
/**
|
|
80964
81060
|
* Last-mile Slack formatting (§9.1 / §9.3). Two pure helpers:
|
|
@@ -82711,8 +82807,12 @@ function parseUsage(raw) {
|
|
|
82711
82807
|
function createSessionReader(store) {
|
|
82712
82808
|
return {
|
|
82713
82809
|
list(req) {
|
|
82714
|
-
|
|
82810
|
+
const rows = store.listSessions(req.agentId);
|
|
82811
|
+
const names = store.getDisplayNames(rows.flatMap((r) => [r.channel, ...r.triggeredBy ? [r.triggeredBy] : []]));
|
|
82812
|
+
return { sessions: rows.map((r) => {
|
|
82715
82813
|
const usage = parseUsage(r.usage);
|
|
82814
|
+
const channelName = names.get(r.channel);
|
|
82815
|
+
const triggeredByName = r.triggeredBy ? names.get(r.triggeredBy) : void 0;
|
|
82716
82816
|
return {
|
|
82717
82817
|
sessionId: r.acpSessionId,
|
|
82718
82818
|
sessionKey: {
|
|
@@ -82723,7 +82823,10 @@ function createSessionReader(store) {
|
|
|
82723
82823
|
agentId: r.agentId,
|
|
82724
82824
|
status: r.state,
|
|
82725
82825
|
lastActivityAt: new Date(r.updatedAt).toISOString(),
|
|
82726
|
-
...usage ? { usage } : {}
|
|
82826
|
+
...usage ? { usage } : {},
|
|
82827
|
+
...r.triggeredBy ? { triggeredBy: r.triggeredBy } : {},
|
|
82828
|
+
...channelName ? { channelName } : {},
|
|
82829
|
+
...triggeredByName ? { triggeredByName } : {}
|
|
82727
82830
|
};
|
|
82728
82831
|
}) };
|
|
82729
82832
|
},
|
|
@@ -82735,13 +82838,19 @@ function createSessionReader(store) {
|
|
|
82735
82838
|
};
|
|
82736
82839
|
const before = req.cursor != null && Number.isFinite(Number(req.cursor)) ? Number(req.cursor) : null;
|
|
82737
82840
|
const { rows, hasMore } = store.transcriptPage(rec.channel, rec.thread, before, req.limit);
|
|
82738
|
-
const
|
|
82739
|
-
|
|
82740
|
-
|
|
82741
|
-
|
|
82742
|
-
|
|
82743
|
-
|
|
82744
|
-
|
|
82841
|
+
const ordered = rows.slice().reverse();
|
|
82842
|
+
const names = store.getDisplayNames(ordered.map((r) => r.sender));
|
|
82843
|
+
const messages = ordered.map((r) => {
|
|
82844
|
+
const senderName = names.get(r.sender);
|
|
82845
|
+
return {
|
|
82846
|
+
seq: r.seq,
|
|
82847
|
+
sender: r.sender,
|
|
82848
|
+
...senderName ? { senderName } : {},
|
|
82849
|
+
ts: r.ts,
|
|
82850
|
+
kind: r.kind,
|
|
82851
|
+
text: r.text
|
|
82852
|
+
};
|
|
82853
|
+
});
|
|
82745
82854
|
return {
|
|
82746
82855
|
sessionId: req.sessionId,
|
|
82747
82856
|
messages,
|
|
@@ -83178,6 +83287,7 @@ var Daemon = class Daemon {
|
|
|
83178
83287
|
fileAgents = /* @__PURE__ */ new Map();
|
|
83179
83288
|
hosts = /* @__PURE__ */ new Map();
|
|
83180
83289
|
connections = [];
|
|
83290
|
+
nameResolver;
|
|
83181
83291
|
scheduler;
|
|
83182
83292
|
sessions;
|
|
83183
83293
|
connByIntegration = /* @__PURE__ */ new Map();
|
|
@@ -83247,6 +83357,7 @@ var Daemon = class Daemon {
|
|
|
83247
83357
|
const skipped = Object.keys(resolvedRuntimes).filter((id) => !this.runtimes[id]);
|
|
83248
83358
|
if (skipped.length) this.log.info(`runtimes not installed (skipped): ${skipped.join(", ")}`);
|
|
83249
83359
|
this.store = new LocalStore(statePath(root));
|
|
83360
|
+
this.nameResolver = new SlackNameResolver((id, name) => this.store.setDisplayName(id, name, Date.now()), this.log);
|
|
83250
83361
|
this.cpRouting = new CpRoutingLayer({
|
|
83251
83362
|
load: () => {
|
|
83252
83363
|
const row = this.store.getCpRouting();
|
|
@@ -83321,7 +83432,10 @@ var Daemon = class Daemon {
|
|
|
83321
83432
|
const conn = new SlackConnection({
|
|
83322
83433
|
group,
|
|
83323
83434
|
newTraceId: () => randomUUID(),
|
|
83324
|
-
onMessage: (msg) =>
|
|
83435
|
+
onMessage: (msg) => {
|
|
83436
|
+
this.nameResolver?.noteMessage(conn, msg);
|
|
83437
|
+
this.onInbound(msg);
|
|
83438
|
+
},
|
|
83325
83439
|
onChannelsChanged: () => void this.refreshChannels(conn),
|
|
83326
83440
|
log: this.log,
|
|
83327
83441
|
boltDebug: cfg.logging.level === "debug" || cfg.logging.level === "trace"
|
|
@@ -83495,7 +83609,10 @@ var Daemon = class Daemon {
|
|
|
83495
83609
|
const conn = new SlackConnection({
|
|
83496
83610
|
group,
|
|
83497
83611
|
newTraceId: () => randomUUID(),
|
|
83498
|
-
onMessage: (msg) =>
|
|
83612
|
+
onMessage: (msg) => {
|
|
83613
|
+
this.nameResolver?.noteMessage(conn, msg);
|
|
83614
|
+
this.onInbound(msg);
|
|
83615
|
+
},
|
|
83499
83616
|
onChannelsChanged: () => void this.refreshChannels(conn),
|
|
83500
83617
|
log: this.log,
|
|
83501
83618
|
boltDebug: this.cfg.logging.level === "debug" || this.cfg.logging.level === "trace"
|
|
@@ -83526,6 +83643,7 @@ var Daemon = class Daemon {
|
|
|
83526
83643
|
try {
|
|
83527
83644
|
const channels = await conn.listBotChannels();
|
|
83528
83645
|
if (!channels) return;
|
|
83646
|
+
for (const c of channels) if (c.name) this.store.setDisplayName(c.id, c.name, Date.now());
|
|
83529
83647
|
for (const [integrationId, c] of this.connByIntegration) {
|
|
83530
83648
|
if (c !== conn) continue;
|
|
83531
83649
|
this.channelSnapshots.set(integrationId, channels);
|