@agentconnect.md/daemon 1.0.0-rc.57 → 1.0.0-rc.59

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 CHANGED
@@ -23639,7 +23639,7 @@ var LocalStore = class {
23639
23639
  CREATE TABLE IF NOT EXISTS sessions (
23640
23640
  key TEXT PRIMARY KEY, agentId TEXT, platform TEXT, channel TEXT, thread TEXT,
23641
23641
  acpSessionId TEXT, state TEXT, lastDeliveredTs TEXT, updatedAt INTEGER,
23642
- usage TEXT
23642
+ usage TEXT, muted INTEGER
23643
23643
  );
23644
23644
  CREATE TABLE IF NOT EXISTS transcript (
23645
23645
  seq INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -23658,11 +23658,14 @@ var LocalStore = class {
23658
23658
  `);
23659
23659
  this.migrateSessionUsage();
23660
23660
  }
23661
- /** Add the `sessions.usage` column (JSON token accounting) to a pre-existing DB.
23662
- * CREATE TABLE IF NOT EXISTS above adds it for fresh DBs but never alters an
23663
- * existing table, so upgrade in place. No-op once the column is present. */
23661
+ /** Add the `sessions.usage` (JSON token accounting) and `sessions.muted` (`!stop`
23662
+ * thread mute) columns to a pre-existing DB. CREATE TABLE IF NOT EXISTS above adds
23663
+ * them for fresh DBs but never alters an existing table, so upgrade in place.
23664
+ * No-op once the columns are present. */
23664
23665
  migrateSessionUsage() {
23665
- if (!this.db.prepare("PRAGMA table_info(sessions)").all().some((c) => c.name === "usage")) this.db.exec("ALTER TABLE sessions ADD COLUMN usage TEXT");
23666
+ const cols = this.db.prepare("PRAGMA table_info(sessions)").all();
23667
+ if (!cols.some((c) => c.name === "usage")) this.db.exec("ALTER TABLE sessions ADD COLUMN usage TEXT");
23668
+ if (!cols.some((c) => c.name === "muted")) this.db.exec("ALTER TABLE sessions ADD COLUMN muted INTEGER");
23666
23669
  }
23667
23670
  /**
23668
23671
  * Pre-`kind` transcript tables keyed on (channel, thread, ts) hold only conversational
@@ -23734,6 +23737,15 @@ var LocalStore = class {
23734
23737
  setSessionState(key, state, updatedAt) {
23735
23738
  this.db.prepare("UPDATE sessions SET state = ?, updatedAt = ? WHERE key = ?").run(state, updatedAt, key);
23736
23739
  }
23740
+ /** `!stop` thread mute: while set, the router must not auto-dispatch this session's
23741
+ * thread to the agent (thread affinity / keyword / auto); an explicit @mention
23742
+ * clears it. No-op on an unknown key. */
23743
+ setSessionMuted(key, muted) {
23744
+ this.db.prepare("UPDATE sessions SET muted = ? WHERE key = ?").run(muted ? 1 : 0, key);
23745
+ }
23746
+ isSessionMuted(key) {
23747
+ return this.db.prepare("SELECT muted FROM sessions WHERE key = ?").get(key)?.muted === 1;
23748
+ }
23737
23749
  /** Current token accounting for a session (parsed from the `usage` JSON column),
23738
23750
  * or `{}` if none has been recorded / the JSON is unreadable. */
23739
23751
  getUsage(key) {
@@ -24449,9 +24461,10 @@ function kindMatches(r, msg) {
24449
24461
  case "auto": return true;
24450
24462
  }
24451
24463
  }
24452
- const pickRule = (r) => ({
24464
+ const pickRule = (r, via) => ({
24453
24465
  agentId: r.agentId,
24454
- integrationId: r.integrationId
24466
+ integrationId: r.integrationId,
24467
+ via
24455
24468
  });
24456
24469
  /**
24457
24470
  * Arbitrate the merged (local ∪ CP) rule set for an inbound message (design §8.2/§8.3).
@@ -24466,19 +24479,19 @@ function routeRules(msg, rules, threadOwner) {
24466
24479
  const scopeCandidates = rules.filter((r) => scopeMatches(r, msg) && authz(r));
24467
24480
  const kindCandidates = scopeCandidates.filter((r) => kindMatches(r, msg));
24468
24481
  const mention = kindCandidates.find((r) => r.match.kind === "mention");
24469
- if (mention) return pickRule(mention);
24482
+ if (mention) return pickRule(mention, "mention");
24470
24483
  if (msg.thread) {
24471
24484
  const owner = threadOwner(msg.channel, msg.thread);
24472
24485
  if (owner) {
24473
24486
  const ownerRule = scopeCandidates.find((x) => x.agentId === owner);
24474
- if (ownerRule) return pickRule(ownerRule);
24487
+ if (ownerRule) return pickRule(ownerRule, "thread");
24475
24488
  }
24476
24489
  }
24477
24490
  const layer = kindCandidates.some((r) => r.source === "cp" && r.scope.channel === msg.channel && (r.scope.thread === void 0 || r.scope.thread === msg.thread)) ? kindCandidates.filter((r) => r.source === "cp") : kindCandidates;
24478
24491
  for (const kind of KIND_ORDER) {
24479
24492
  if (kind === "mention") continue;
24480
24493
  const r = layer.find((x) => x.match.kind === kind);
24481
- if (r) return pickRule(r);
24494
+ if (r) return pickRule(r, kind);
24482
24495
  }
24483
24496
  return null;
24484
24497
  }
@@ -83114,6 +83127,16 @@ var Daemon = class Daemon {
83114
83127
  this.log.debug(`routing: dropping ${msg.msgId} for agent "${result.agentId}" (draining)`);
83115
83128
  return;
83116
83129
  }
83130
+ const muteKey = sessionKey(msg.platform, msg.channel, msg.thread ?? msg.msgId, result.agentId);
83131
+ if (this.store.isSessionMuted(muteKey)) {
83132
+ if (result.via !== "mention") {
83133
+ this.recordUnrouted(msg);
83134
+ this.log.debug(`routing: dropping ${msg.msgId} for agent "${result.agentId}" (muted by !stop; awaiting @mention)`);
83135
+ return;
83136
+ }
83137
+ this.store.setSessionMuted(muteKey, false);
83138
+ this.log.info(`routing: agent "${result.agentId}" un-muted in ch=${msg.channel} (explicit @mention)`);
83139
+ }
83117
83140
  this.log.info(`routing: ch=${msg.channel} → agent "${result.agentId}" (integration ${result.integrationId})`);
83118
83141
  this.dispatch(result.agentId, msg, result.integrationId).catch((err) => this.log.error(`dispatch failed for agent "${result.agentId}": ${formatErr(err)}`));
83119
83142
  }
@@ -83184,11 +83207,14 @@ var Daemon = class Daemon {
83184
83207
  const conn = this.replyConnFor(target.agentId, target.integrationId);
83185
83208
  const thread = msg.thread ?? msg.msgId;
83186
83209
  const key = sessionKey(msg.platform, msg.channel, thread, target.agentId);
83187
- const acpSessionId = this.store.getSession(key)?.acpSessionId;
83210
+ const rec = this.store.getSession(key);
83211
+ const acpSessionId = rec?.acpSessionId;
83188
83212
  const inflight = !!(acpSessionId && this.pending.has(acpSessionId));
83189
83213
  if (command.kind === "stop") {
83214
+ if (rec) this.store.setSessionMuted(key, true);
83215
+ const muteNote = "Muted in this thread — @mention me to resume.";
83190
83216
  if (!inflight) {
83191
- conn?.postMessage(msg.channel, "Nothing is running to stop.", thread);
83217
+ conn?.postMessage(msg.channel, rec ? `🔇 Nothing is running. ${muteNote}` : "Nothing is running to stop.", thread);
83192
83218
  return;
83193
83219
  }
83194
83220
  this.queued.delete(acpSessionId);
@@ -83196,7 +83222,7 @@ var Daemon = class Daemon {
83196
83222
  this.store.setSessionState(key, "cancelling", this.clock.now());
83197
83223
  this.hosts.get(target.agentId)?.cancel(acpSessionId).catch((err) => this.log.error(`command stop: cancel failed: ${err.message}`));
83198
83224
  this.armCancelBackstop(target.agentId, acpSessionId, key);
83199
- conn?.postMessage(msg.channel, "🛑 Stopped.", thread);
83225
+ conn?.postMessage(msg.channel, `🛑 Stopped. ${muteNote}`, thread);
83200
83226
  return;
83201
83227
  }
83202
83228
  if (!command.text) {