@agentconnect.md/daemon 1.0.0-rc.15 → 1.0.0-rc.17
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 +96 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23126,6 +23126,34 @@ function routeRules(msg, rules, threadOwner) {
|
|
|
23126
23126
|
return null;
|
|
23127
23127
|
}
|
|
23128
23128
|
//#endregion
|
|
23129
|
+
//#region src/commands/commands.ts
|
|
23130
|
+
/** Accepted command prefixes (Slack uses `!`; `/` is for future platforms). */
|
|
23131
|
+
const COMMAND_PREFIXES = ["!", "/"];
|
|
23132
|
+
const STOP_WORDS = /* @__PURE__ */ new Set(["stop", "cancel"]);
|
|
23133
|
+
const QUEUE_WORDS = /* @__PURE__ */ new Set(["queue"]);
|
|
23134
|
+
/**
|
|
23135
|
+
* Parse a leading control command from a message's text. Returns `null` when the
|
|
23136
|
+
* text is not a recognized command (so it flows to the agent unchanged). The
|
|
23137
|
+
* prefix must be the first non-whitespace character and be followed immediately by
|
|
23138
|
+
* a known command word, so ordinary text like `hello!` or `! note` is never a
|
|
23139
|
+
* command.
|
|
23140
|
+
*/
|
|
23141
|
+
function parseCommand(raw) {
|
|
23142
|
+
const text = raw.trimStart();
|
|
23143
|
+
const prefix = COMMAND_PREFIXES.find((p) => text.startsWith(p));
|
|
23144
|
+
if (!prefix) return null;
|
|
23145
|
+
const m = /^([a-zA-Z]+)([\s\S]*)$/.exec(text.slice(prefix.length));
|
|
23146
|
+
if (!m) return null;
|
|
23147
|
+
const word = m[1].toLowerCase();
|
|
23148
|
+
const arg = (m[2] ?? "").trim();
|
|
23149
|
+
if (STOP_WORDS.has(word)) return { kind: "stop" };
|
|
23150
|
+
if (QUEUE_WORDS.has(word)) return {
|
|
23151
|
+
kind: "queue",
|
|
23152
|
+
text: arg
|
|
23153
|
+
};
|
|
23154
|
+
return null;
|
|
23155
|
+
}
|
|
23156
|
+
//#endregion
|
|
23129
23157
|
//#region src/router/routing-rule.ts
|
|
23130
23158
|
/**
|
|
23131
23159
|
* Resolve an agent to its first Slack integration's `{ integrationId, botUserId }`.
|
|
@@ -80536,6 +80564,7 @@ const LOADING_MSGS = [
|
|
|
80536
80564
|
"Crunching through it…",
|
|
80537
80565
|
"Hang tight…"
|
|
80538
80566
|
];
|
|
80567
|
+
const MAX_QUEUED_PER_SESSION = 10;
|
|
80539
80568
|
var Daemon = class {
|
|
80540
80569
|
opts;
|
|
80541
80570
|
store;
|
|
@@ -80697,6 +80726,11 @@ var Daemon = class {
|
|
|
80697
80726
|
}
|
|
80698
80727
|
this.seenMsgIds.add(msg.msgId);
|
|
80699
80728
|
if (this.seenMsgIds.size > 2e3) this.seenMsgIds.clear();
|
|
80729
|
+
const command = parseCommand(msg.text);
|
|
80730
|
+
if (command) {
|
|
80731
|
+
this.handleCommand(command, msg);
|
|
80732
|
+
return;
|
|
80733
|
+
}
|
|
80700
80734
|
const result = routeRules(msg, this.mergedRules(), (c, t) => this.sessions.threadOwner(c, t));
|
|
80701
80735
|
if (!result) {
|
|
80702
80736
|
this.log.debug(`routing: dropped message in ch=${msg.channel} (no agent matched — not a mention of a known bot, not a subscribed 'all' channel, not a thread/DM hit)`);
|
|
@@ -80705,6 +80739,67 @@ var Daemon = class {
|
|
|
80705
80739
|
this.log.info(`routing: ch=${msg.channel} → agent "${result.agentId}" (integration ${result.integrationId})`);
|
|
80706
80740
|
this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${err.stack ?? err}`));
|
|
80707
80741
|
}
|
|
80742
|
+
queued = /* @__PURE__ */ new Map();
|
|
80743
|
+
/**
|
|
80744
|
+
* Handle an in-conversation control command. Resolves the target agent via the
|
|
80745
|
+
* same routing ladder as a normal message (so thread-affinity + per-integration
|
|
80746
|
+
* `allowedUserIds` authz apply), then acts on that agent's session in this
|
|
80747
|
+
* (channel, thread).
|
|
80748
|
+
*/
|
|
80749
|
+
handleCommand(command, msg) {
|
|
80750
|
+
const target = routeRules(msg, this.mergedRules(), (c, t) => this.sessions.threadOwner(c, t));
|
|
80751
|
+
if (!target) {
|
|
80752
|
+
this.log.debug(`command: '${command.kind}' in ch=${msg.channel} — no agent resolved, ignoring`);
|
|
80753
|
+
return;
|
|
80754
|
+
}
|
|
80755
|
+
const conn = this.replyConnFor(target.agentId, target.integrationId);
|
|
80756
|
+
const thread = msg.thread ?? msg.msgId;
|
|
80757
|
+
const acpSessionId = this.store.getSession(sessionKey(msg.platform, msg.channel, thread, target.agentId))?.acpSessionId;
|
|
80758
|
+
const inflight = !!(acpSessionId && this.pending.has(acpSessionId));
|
|
80759
|
+
if (command.kind === "stop") {
|
|
80760
|
+
if (!inflight) {
|
|
80761
|
+
conn?.postMessage(msg.channel, "Nothing is running to stop.", thread);
|
|
80762
|
+
return;
|
|
80763
|
+
}
|
|
80764
|
+
this.queued.delete(acpSessionId);
|
|
80765
|
+
this.log.info(`command: stop → agent "${target.agentId}" session ${acpSessionId}`);
|
|
80766
|
+
this.hosts.get(target.agentId)?.cancel(acpSessionId).catch((err) => this.log.error(`command stop: cancel failed: ${err.message}`));
|
|
80767
|
+
conn?.postMessage(msg.channel, "🛑 Stopped.", thread);
|
|
80768
|
+
return;
|
|
80769
|
+
}
|
|
80770
|
+
if (!command.text) {
|
|
80771
|
+
conn?.postMessage(msg.channel, "Usage: `!queue <message>` — runs when the current turn finishes.", thread);
|
|
80772
|
+
return;
|
|
80773
|
+
}
|
|
80774
|
+
const payload = {
|
|
80775
|
+
...msg,
|
|
80776
|
+
text: command.text
|
|
80777
|
+
};
|
|
80778
|
+
if (!inflight) {
|
|
80779
|
+
this.log.info(`command: queue → agent "${target.agentId}" idle, dispatching now`);
|
|
80780
|
+
this.dispatch(target.agentId, payload, target.integrationId).catch((err) => this.log.error(`queued dispatch failed for agent "${target.agentId}": ${err.stack ?? err}`));
|
|
80781
|
+
return;
|
|
80782
|
+
}
|
|
80783
|
+
const q = this.queued.get(acpSessionId) ?? [];
|
|
80784
|
+
if (q.length >= MAX_QUEUED_PER_SESSION) {
|
|
80785
|
+
this.log.warn(`command: queue → agent "${target.agentId}" session ${acpSessionId} full (${q.length}), rejected`);
|
|
80786
|
+
conn?.postMessage(msg.channel, `Queue is full (${MAX_QUEUED_PER_SESSION} pending) — wait for the current turn to finish.`, thread);
|
|
80787
|
+
return;
|
|
80788
|
+
}
|
|
80789
|
+
q.push(payload);
|
|
80790
|
+
this.queued.set(acpSessionId, q);
|
|
80791
|
+
this.log.info(`command: queue → agent "${target.agentId}" session ${acpSessionId} (depth ${q.length})`);
|
|
80792
|
+
conn?.postMessage(msg.channel, `📥 Queued (#${q.length}) — will run when the current turn finishes.`, thread);
|
|
80793
|
+
}
|
|
80794
|
+
/** Drain one buffered message for a session whose turn just finished (FIFO). */
|
|
80795
|
+
flushQueued(agentId, sessionId, integrationId) {
|
|
80796
|
+
const q = this.queued.get(sessionId);
|
|
80797
|
+
if (!q || q.length === 0) return;
|
|
80798
|
+
const next = q.shift();
|
|
80799
|
+
if (q.length === 0) this.queued.delete(sessionId);
|
|
80800
|
+
this.log.info(`queue: dispatching buffered message to agent "${agentId}" session ${sessionId} (${q.length} left)`);
|
|
80801
|
+
this.dispatch(agentId, next, integrationId).catch((err) => this.log.error(`queued dispatch failed for agent "${agentId}": ${err.stack ?? err}`));
|
|
80802
|
+
}
|
|
80708
80803
|
/** Local layer (agent.json) ∪ resolved CP layer; unservable CP rules are dropped + warn-logged. */
|
|
80709
80804
|
mergedRules() {
|
|
80710
80805
|
const local = [...this.agents.values()].flatMap((a) => rulesFromAgent(a, this.botUserIds));
|
|
@@ -80747,6 +80842,7 @@ var Daemon = class {
|
|
|
80747
80842
|
} finally {
|
|
80748
80843
|
this.pending.delete(sessionId);
|
|
80749
80844
|
}
|
|
80845
|
+
this.flushQueued(agentId, sessionId, integrationId);
|
|
80750
80846
|
}
|
|
80751
80847
|
/** Route a converger action: set-status → setStatus (loading_messages only when not clearing); else postMessage. */
|
|
80752
80848
|
async applyAction(action, conn, channel, thread) {
|