openryoko 2026.6.9 → 2026.6.10

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.
Files changed (36) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/connectors/slack/__tests__/active-threads.test.d.ts +2 -0
  3. package/dist/src/connectors/slack/__tests__/active-threads.test.d.ts.map +1 -0
  4. package/dist/src/connectors/slack/__tests__/active-threads.test.js +57 -0
  5. package/dist/src/connectors/slack/__tests__/active-threads.test.js.map +1 -0
  6. package/dist/src/connectors/slack/__tests__/dm-equivalent.test.d.ts +2 -0
  7. package/dist/src/connectors/slack/__tests__/dm-equivalent.test.d.ts.map +1 -0
  8. package/dist/src/connectors/slack/__tests__/dm-equivalent.test.js +72 -0
  9. package/dist/src/connectors/slack/__tests__/dm-equivalent.test.js.map +1 -0
  10. package/dist/src/connectors/slack/active-threads.d.ts +39 -0
  11. package/dist/src/connectors/slack/active-threads.d.ts.map +1 -0
  12. package/dist/src/connectors/slack/active-threads.js +71 -0
  13. package/dist/src/connectors/slack/active-threads.js.map +1 -0
  14. package/dist/src/connectors/slack/dm-equivalent.d.ts +44 -0
  15. package/dist/src/connectors/slack/dm-equivalent.d.ts.map +1 -0
  16. package/dist/src/connectors/slack/dm-equivalent.js +69 -0
  17. package/dist/src/connectors/slack/dm-equivalent.js.map +1 -0
  18. package/dist/src/connectors/slack/index.d.ts +7 -0
  19. package/dist/src/connectors/slack/index.d.ts.map +1 -1
  20. package/dist/src/connectors/slack/index.js +30 -14
  21. package/dist/src/connectors/slack/index.js.map +1 -1
  22. package/dist/src/sessions/__tests__/reply-disposition.test.d.ts +2 -0
  23. package/dist/src/sessions/__tests__/reply-disposition.test.d.ts.map +1 -0
  24. package/dist/src/sessions/__tests__/reply-disposition.test.js +149 -0
  25. package/dist/src/sessions/__tests__/reply-disposition.test.js.map +1 -0
  26. package/dist/src/sessions/context.js +7 -2
  27. package/dist/src/sessions/context.js.map +1 -1
  28. package/dist/src/sessions/manager.d.ts +9 -0
  29. package/dist/src/sessions/manager.d.ts.map +1 -1
  30. package/dist/src/sessions/manager.js +51 -14
  31. package/dist/src/sessions/manager.js.map +1 -1
  32. package/dist/src/sessions/reply-disposition.d.ts +97 -0
  33. package/dist/src/sessions/reply-disposition.d.ts.map +1 -0
  34. package/dist/src/sessions/reply-disposition.js +160 -0
  35. package/dist/src/sessions/reply-disposition.js.map +1 -0
  36. package/package.json +1 -1
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openryoko",
3
- "version": "2026.6.9",
3
+ "version": "2026.6.10",
4
4
  "description": "OpenRyoko — Slackで空気を読んで働くAIアシスタント・ゲートウェイ(Jinnベース、Claude Code/Codex/Gemini CLI対応)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=active-threads.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-threads.test.d.ts","sourceRoot":"","sources":["../../../../../src/connectors/slack/__tests__/active-threads.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ActiveThreadTracker } from "../active-threads.js";
3
+ describe("ActiveThreadTracker", () => {
4
+ it("reports a freshly-touched thread as active", () => {
5
+ const t = new ActiveThreadTracker(60_000);
6
+ t.touch("C1", "1234.5678", 1_000_000);
7
+ expect(t.isActive("C1", "1234.5678", 1_000_000)).toBe(true);
8
+ });
9
+ it("considers a thread inactive once TTL expires", () => {
10
+ const t = new ActiveThreadTracker(60_000);
11
+ t.touch("C1", "1234.5678", 1_000_000);
12
+ // 61s later
13
+ expect(t.isActive("C1", "1234.5678", 1_061_000)).toBe(false);
14
+ });
15
+ it("does not leak state between channels", () => {
16
+ const t = new ActiveThreadTracker(60_000);
17
+ t.touch("C1", "1234.5678", 1_000_000);
18
+ expect(t.isActive("C2", "1234.5678", 1_000_000)).toBe(false);
19
+ });
20
+ it("does not leak state between thread keys", () => {
21
+ const t = new ActiveThreadTracker(60_000);
22
+ t.touch("C1", "1234.5678", 1_000_000);
23
+ expect(t.isActive("C1", "9999.9999", 1_000_000)).toBe(false);
24
+ });
25
+ it("safely no-ops when channel or threadKey is missing", () => {
26
+ const t = new ActiveThreadTracker(60_000);
27
+ t.touch("", "1234.5678");
28
+ t.touch("C1", undefined);
29
+ expect(t.isActive("", "1234.5678")).toBe(false);
30
+ expect(t.isActive("C1", undefined)).toBe(false);
31
+ expect(t.size()).toBe(0);
32
+ });
33
+ it("refreshes the timestamp when a thread is touched again", () => {
34
+ const t = new ActiveThreadTracker(60_000);
35
+ t.touch("C1", "1234.5678", 1_000_000);
36
+ t.touch("C1", "1234.5678", 1_050_000);
37
+ // original TTL from first touch would have expired at 1_060_000,
38
+ // but second touch resets it, so at 1_100_000 it should still be active
39
+ expect(t.isActive("C1", "1234.5678", 1_100_000)).toBe(true);
40
+ // and expire at 1_050_000 + 60_000 = 1_110_000
41
+ expect(t.isActive("C1", "1234.5678", 1_111_000)).toBe(false);
42
+ });
43
+ it("prunes expired entries when size exceeds internal threshold", () => {
44
+ // Drive an intentional growth past PRUNE_AT_SIZE (1000) and confirm
45
+ // the map never explodes unbounded.
46
+ const t = new ActiveThreadTracker(1); // 1ms TTL
47
+ for (let i = 0; i < 1500; i++) {
48
+ t.touch("C1", `ts-${i}`, 0);
49
+ }
50
+ // At touch #1001 the internal prune runs and wipes everything (TTL=1ms, now=0
51
+ // means first entry is technically not expired, but subsequent touches at now=0
52
+ // stay fresh. Simulate expiry via manual prune at a later timestamp.
53
+ t.prune(1_000_000);
54
+ expect(t.size()).toBe(0);
55
+ });
56
+ });
57
+ //# sourceMappingURL=active-threads.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-threads.test.js","sourceRoot":"","sources":["../../../../../src/connectors/slack/__tests__/active-threads.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dm-equivalent.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dm-equivalent.test.d.ts","sourceRoot":"","sources":["../../../../../src/connectors/slack/__tests__/dm-equivalent.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { DmEquivalentDetector } from "../dm-equivalent.js";
3
+ function makeClient(responses) {
4
+ let i = 0;
5
+ const info = vi.fn().mockImplementation(async (_args) => {
6
+ const next = responses[i++] ?? responses[responses.length - 1];
7
+ if (next instanceof Error)
8
+ throw next;
9
+ return { ok: true, channel: next };
10
+ });
11
+ return { info };
12
+ }
13
+ describe("DmEquivalentDetector", () => {
14
+ it("returns true when the channel has exactly 2 members", async () => {
15
+ const client = makeClient([{ num_members: 2 }]);
16
+ const det = new DmEquivalentDetector(client);
17
+ expect(await det.isTwoMember("C123")).toBe(true);
18
+ });
19
+ it("returns false when the channel has more than 2 members", async () => {
20
+ const client = makeClient([{ num_members: 5 }]);
21
+ const det = new DmEquivalentDetector(client);
22
+ expect(await det.isTwoMember("C123")).toBe(false);
23
+ });
24
+ it("returns null on Slack API failure (caller should treat as 'unknown')", async () => {
25
+ const client = makeClient([new Error("not_in_channel")]);
26
+ const det = new DmEquivalentDetector(client);
27
+ expect(await det.isTwoMember("C123")).toBeNull();
28
+ });
29
+ it("returns null when num_members is missing from the response", async () => {
30
+ const client = makeClient([{}]);
31
+ const det = new DmEquivalentDetector(client);
32
+ expect(await det.isTwoMember("C123")).toBeNull();
33
+ });
34
+ it("caches results across calls within the TTL window", async () => {
35
+ const client = makeClient([{ num_members: 2 }]);
36
+ const det = new DmEquivalentDetector(client, 60_000);
37
+ await det.isTwoMember("C123");
38
+ await det.isTwoMember("C123");
39
+ await det.isTwoMember("C123");
40
+ expect(client.info).toHaveBeenCalledTimes(1);
41
+ });
42
+ it("re-fetches once the TTL expires", async () => {
43
+ const client = makeClient([{ num_members: 2 }, { num_members: 5 }]);
44
+ const det = new DmEquivalentDetector(client, 1_000);
45
+ const start = 1_000_000_000;
46
+ expect(await det.isTwoMember("C123", start)).toBe(true);
47
+ expect(await det.isTwoMember("C123", start + 1_500)).toBe(false);
48
+ expect(client.info).toHaveBeenCalledTimes(2);
49
+ });
50
+ it("does not cache failures (so transient errors recover on the next message)", async () => {
51
+ const client = makeClient([new Error("rate_limited"), { num_members: 2 }]);
52
+ const det = new DmEquivalentDetector(client);
53
+ expect(await det.isTwoMember("C123")).toBeNull();
54
+ expect(await det.isTwoMember("C123")).toBe(true);
55
+ expect(client.info).toHaveBeenCalledTimes(2);
56
+ });
57
+ it("invalidate() drops the cached entry", async () => {
58
+ const client = makeClient([{ num_members: 2 }, { num_members: 3 }]);
59
+ const det = new DmEquivalentDetector(client);
60
+ expect(await det.isTwoMember("C123")).toBe(true);
61
+ det.invalidate("C123");
62
+ expect(await det.isTwoMember("C123")).toBe(false);
63
+ expect(client.info).toHaveBeenCalledTimes(2);
64
+ });
65
+ it("returns null for empty channel id without calling the API", async () => {
66
+ const client = makeClient([{ num_members: 2 }]);
67
+ const det = new DmEquivalentDetector(client);
68
+ expect(await det.isTwoMember("")).toBeNull();
69
+ expect(client.info).not.toHaveBeenCalled();
70
+ });
71
+ });
72
+ //# sourceMappingURL=dm-equivalent.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dm-equivalent.test.js","sourceRoot":"","sources":["../../../../../src/connectors/slack/__tests__/dm-equivalent.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Tracks which Slack threads the bot has recently participated in,
3
+ * so the triage layer can skip air-reading for threads that are
4
+ * already mid-conversation (e.g. a skill is in progress, or the user
5
+ * is following up on a prior reply).
6
+ *
7
+ * Keyed by `${channelId}:${threadTs}`. `threadTs` is the root ts of
8
+ * a Slack thread — for a bot reply, it's whatever we passed as
9
+ * `thread_ts` in `chat.postMessage`; for a bot's root message, it's
10
+ * the ts of that message (since future replies will use it as
11
+ * thread_ts).
12
+ */
13
+ export declare class ActiveThreadTracker {
14
+ private readonly entries;
15
+ private readonly ttlMs;
16
+ /**
17
+ * Prune when the map grows past this size. Cheap defense against
18
+ * unbounded growth in long-running daemons.
19
+ */
20
+ private static readonly PRUNE_AT_SIZE;
21
+ constructor(ttlMs?: number);
22
+ private makeKey;
23
+ /**
24
+ * Record that the bot just did something in this thread (reply, react, etc.).
25
+ * `threadKey` should be the thread_ts the bot used, or the ts of a new
26
+ * root message the bot posted (so future replies that use it as thread_ts
27
+ * will match).
28
+ */
29
+ touch(channel: string, threadKey: string | undefined, now?: number): void;
30
+ /**
31
+ * Is this thread currently considered active (bot participated within TTL)?
32
+ */
33
+ isActive(channel: string, threadKey: string | undefined, now?: number): boolean;
34
+ /** Remove entries older than TTL. */
35
+ prune(now?: number): void;
36
+ /** For tests / debugging. */
37
+ size(): number;
38
+ }
39
+ //# sourceMappingURL=active-threads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-threads.d.ts","sourceRoot":"","sources":["../../../../src/connectors/slack/active-threads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAQ;gBAEjC,KAAK,GAAE,MAAuB;IAI1C,OAAO,CAAC,OAAO;IAIf;;;;;OAKG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,IAAI;IAQrF;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO;IAY3F,qCAAqC;IACrC,KAAK,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI;IAQrC,6BAA6B;IAC7B,IAAI,IAAI,MAAM;CAGf"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Tracks which Slack threads the bot has recently participated in,
3
+ * so the triage layer can skip air-reading for threads that are
4
+ * already mid-conversation (e.g. a skill is in progress, or the user
5
+ * is following up on a prior reply).
6
+ *
7
+ * Keyed by `${channelId}:${threadTs}`. `threadTs` is the root ts of
8
+ * a Slack thread — for a bot reply, it's whatever we passed as
9
+ * `thread_ts` in `chat.postMessage`; for a bot's root message, it's
10
+ * the ts of that message (since future replies will use it as
11
+ * thread_ts).
12
+ */
13
+ const DEFAULT_TTL_MS = 600_000; // 10 minutes
14
+ export class ActiveThreadTracker {
15
+ entries = new Map();
16
+ ttlMs;
17
+ /**
18
+ * Prune when the map grows past this size. Cheap defense against
19
+ * unbounded growth in long-running daemons.
20
+ */
21
+ static PRUNE_AT_SIZE = 1000;
22
+ constructor(ttlMs = DEFAULT_TTL_MS) {
23
+ this.ttlMs = ttlMs;
24
+ }
25
+ makeKey(channel, threadKey) {
26
+ return `${channel}:${threadKey}`;
27
+ }
28
+ /**
29
+ * Record that the bot just did something in this thread (reply, react, etc.).
30
+ * `threadKey` should be the thread_ts the bot used, or the ts of a new
31
+ * root message the bot posted (so future replies that use it as thread_ts
32
+ * will match).
33
+ */
34
+ touch(channel, threadKey, now = Date.now()) {
35
+ if (!channel || !threadKey)
36
+ return;
37
+ this.entries.set(this.makeKey(channel, threadKey), now);
38
+ if (this.entries.size > ActiveThreadTracker.PRUNE_AT_SIZE) {
39
+ this.prune(now);
40
+ }
41
+ }
42
+ /**
43
+ * Is this thread currently considered active (bot participated within TTL)?
44
+ */
45
+ isActive(channel, threadKey, now = Date.now()) {
46
+ if (!channel || !threadKey)
47
+ return false;
48
+ const lastSeen = this.entries.get(this.makeKey(channel, threadKey));
49
+ if (lastSeen === undefined)
50
+ return false;
51
+ if (now - lastSeen > this.ttlMs) {
52
+ // lazy cleanup of this specific entry
53
+ this.entries.delete(this.makeKey(channel, threadKey));
54
+ return false;
55
+ }
56
+ return true;
57
+ }
58
+ /** Remove entries older than TTL. */
59
+ prune(now = Date.now()) {
60
+ for (const [key, ts] of this.entries) {
61
+ if (now - ts > this.ttlMs) {
62
+ this.entries.delete(key);
63
+ }
64
+ }
65
+ }
66
+ /** For tests / debugging. */
67
+ size() {
68
+ return this.entries.size;
69
+ }
70
+ }
71
+ //# sourceMappingURL=active-threads.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"active-threads.js","sourceRoot":"","sources":["../../../../src/connectors/slack/active-threads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,aAAa;AAE7C,MAAM,OAAO,mBAAmB;IACb,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,KAAK,CAAS;IAC/B;;;OAGG;IACK,MAAM,CAAU,aAAa,GAAG,IAAI,CAAC;IAE7C,YAAY,QAAgB,cAAc;QACxC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAEO,OAAO,CAAC,OAAe,EAAE,SAAiB;QAChD,OAAO,GAAG,OAAO,IAAI,SAAS,EAAE,CAAC;IACnC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,OAAe,EAAE,SAA6B,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;QAC5E,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS;YAAE,OAAO;QACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,mBAAmB,CAAC,aAAa,EAAE,CAAC;YAC1D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,OAAe,EAAE,SAA6B,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;QAC/E,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;QACpE,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACzC,IAAI,GAAG,GAAG,QAAQ,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAChC,sCAAsC;YACtC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;YACtD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qCAAqC;IACrC,KAAK,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;QAC5B,KAAK,MAAM,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACrC,IAAI,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * DM-equivalent channel detector.
3
+ *
4
+ * Slack's `im` channel type only covers 1:1 native DMs. Operators commonly
5
+ * create a *private channel* with just themselves and the bot — visually
6
+ * identical to a DM, but classified as `channel` by the Slack API. Such
7
+ * channels should bypass air-reading triage for the same reason real DMs do:
8
+ * every message is implicitly addressed to the bot.
9
+ *
10
+ * This module wraps `conversations.info` with a small TTL cache so we can
11
+ * answer "does this channel have exactly the bot + one other member?"
12
+ * cheaply on the hot path.
13
+ */
14
+ interface SlackConversationsInfoClient {
15
+ info(args: {
16
+ channel: string;
17
+ }): Promise<{
18
+ ok?: boolean;
19
+ channel?: {
20
+ num_members?: number;
21
+ };
22
+ }>;
23
+ }
24
+ export declare class DmEquivalentDetector {
25
+ private readonly client;
26
+ private readonly cache;
27
+ private readonly ttlMs;
28
+ constructor(client: SlackConversationsInfoClient, ttlMs?: number);
29
+ /**
30
+ * Resolve whether the channel has exactly 2 members (the bot + one other).
31
+ * Returns `true` / `false` on success, or `null` when membership couldn't
32
+ * be determined — callers should treat `null` as "unknown, fall through to
33
+ * regular triage" rather than as a skip signal.
34
+ */
35
+ isTwoMember(channel: string, now?: number): Promise<boolean | null>;
36
+ /**
37
+ * Drop the cache entry for a channel — useful when a join/leave event
38
+ * tells us the membership just changed.
39
+ */
40
+ invalidate(channel: string): void;
41
+ private prune;
42
+ }
43
+ export {};
44
+ //# sourceMappingURL=dm-equivalent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dm-equivalent.d.ts","sourceRoot":"","sources":["../../../../src/connectors/slack/dm-equivalent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,UAAU,4BAA4B;IACpC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QACvC,EAAE,CAAC,EAAE,OAAO,CAAC;QACb,OAAO,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACpC,CAAC,CAAC;CACJ;AAOD,qBAAa,oBAAoB;IAK7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAGZ,MAAM,EAAE,4BAA4B,EACrD,KAAK,GAAE,MAAuB;IAKhC;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAyBrF;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIjC,OAAO,CAAC,KAAK;CAKd"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * DM-equivalent channel detector.
3
+ *
4
+ * Slack's `im` channel type only covers 1:1 native DMs. Operators commonly
5
+ * create a *private channel* with just themselves and the bot — visually
6
+ * identical to a DM, but classified as `channel` by the Slack API. Such
7
+ * channels should bypass air-reading triage for the same reason real DMs do:
8
+ * every message is implicitly addressed to the bot.
9
+ *
10
+ * This module wraps `conversations.info` with a small TTL cache so we can
11
+ * answer "does this channel have exactly the bot + one other member?"
12
+ * cheaply on the hot path.
13
+ */
14
+ import { logger } from "../../shared/logger.js";
15
+ const DEFAULT_TTL_MS = 600_000; // 10 minutes — channel membership is stable
16
+ const PRUNE_AT_SIZE = 500;
17
+ export class DmEquivalentDetector {
18
+ client;
19
+ cache = new Map();
20
+ ttlMs;
21
+ constructor(client, ttlMs = DEFAULT_TTL_MS) {
22
+ this.client = client;
23
+ this.ttlMs = ttlMs;
24
+ }
25
+ /**
26
+ * Resolve whether the channel has exactly 2 members (the bot + one other).
27
+ * Returns `true` / `false` on success, or `null` when membership couldn't
28
+ * be determined — callers should treat `null` as "unknown, fall through to
29
+ * regular triage" rather than as a skip signal.
30
+ */
31
+ async isTwoMember(channel, now = Date.now()) {
32
+ if (!channel)
33
+ return null;
34
+ const cached = this.cache.get(channel);
35
+ if (cached && cached.expiresAt > now) {
36
+ return cached.numMembers === 2;
37
+ }
38
+ try {
39
+ const result = await this.client.info({ channel });
40
+ const numMembers = result?.channel?.num_members;
41
+ if (typeof numMembers !== "number") {
42
+ return null;
43
+ }
44
+ this.cache.set(channel, { numMembers, expiresAt: now + this.ttlMs });
45
+ if (this.cache.size > PRUNE_AT_SIZE) {
46
+ this.prune(now);
47
+ }
48
+ return numMembers === 2;
49
+ }
50
+ catch (err) {
51
+ logger.debug(`[slack] dm-equivalent lookup failed for ${channel}: ${err}`);
52
+ return null;
53
+ }
54
+ }
55
+ /**
56
+ * Drop the cache entry for a channel — useful when a join/leave event
57
+ * tells us the membership just changed.
58
+ */
59
+ invalidate(channel) {
60
+ this.cache.delete(channel);
61
+ }
62
+ prune(now) {
63
+ for (const [key, entry] of this.cache) {
64
+ if (entry.expiresAt <= now)
65
+ this.cache.delete(key);
66
+ }
67
+ }
68
+ }
69
+ //# sourceMappingURL=dm-equivalent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dm-equivalent.js","sourceRoot":"","sources":["../../../../src/connectors/slack/dm-equivalent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEhD,MAAM,cAAc,GAAG,OAAO,CAAC,CAAC,4CAA4C;AAC5E,MAAM,aAAa,GAAG,GAAG,CAAC;AAc1B,MAAM,OAAO,oBAAoB;IAKZ;IAJF,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IACtC,KAAK,CAAS;IAE/B,YACmB,MAAoC,EACrD,QAAgB,cAAc;QADb,WAAM,GAAN,MAAM,CAA8B;QAGrD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;QACzD,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;YACrC,OAAO,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;YACnD,MAAM,UAAU,GAAG,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC;YAChD,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;gBACnC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YACrE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,aAAa,EAAE,CAAC;gBACpC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,UAAU,KAAK,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,2CAA2C,OAAO,KAAK,GAAG,EAAE,CAAC,CAAC;YAC3E,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,OAAe;QACxB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,GAAW;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;CACF"}
@@ -42,6 +42,13 @@ export declare class SlackConnector implements Connector {
42
42
  private speakerTransportFields;
43
43
  private runSlackTriage;
44
44
  private fetchRecentThreadForTriage;
45
+ /**
46
+ * Resolve channel name and external-shared status in one cached call.
47
+ * `isExtShared` is true for Slack Connect (externally/org shared) channels —
48
+ * the delivery layer treats these (and unknowns) as "external" so the model's
49
+ * operator-facing notes are never posted there. See reply-disposition.ts.
50
+ */
51
+ private resolveChannelInfo;
45
52
  private resolveChannelName;
46
53
  start(): Promise<void>;
47
54
  stop(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/connectors/slack/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,MAAM,EAEP,MAAM,uBAAuB,CAAC;AAa/B,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,qBAAa,cAAe,YAAW,SAAS;IAC9C,IAAI,SAAW;IACf,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,OAAO,CAAiD;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,gBAAgB,CAAyD;IACjF,OAAO,CAAC,aAAa,CAA8D;IACnF,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,eAAe,CAAqD;IAC5E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAwC;IAC7E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAU;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAY;IAC/C,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAY;IAE5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAK3B;IAEF,OAAO,CAAC,gBAAgB;YAUV,gBAAgB;IAkB9B;;;OAGG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gBAoBzF,MAAM,EAAE,oBAAoB,EAAE,OAAO,GAAE,qBAA0B;YAwB/D,kBAAkB;IAiBhC,OAAO,CAAC,sBAAsB;YAYhB,cAAc;YAwDd,0BAA0B;YA0C1B,kBAAkB;IAkB1B,KAAK;IAqWL,IAAI;IAWV,eAAe,IAAI,qBAAqB;IAIxC,SAAS,IAAI,eAAe;IAQ5B;;;;;;;OAOG;IACG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAgCzG,iBAAiB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM;IAS/C,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAoBtE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAuBvE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAoBzC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAa5C,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAU9C,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI;CAGlD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/connectors/slack/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,SAAS,EACT,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,MAAM,EAEP,MAAM,uBAAuB,CAAC;AAa/B,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sFAAsF;IACtF,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,qBAAa,cAAe,YAAW,SAAS;IAC9C,IAAI,SAAW;IACf,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,OAAO,CAAiD;IAChE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,gBAAgB,CAAgF;IACxG,OAAO,CAAC,aAAa,CAA8D;IACnF,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,eAAe,CAAqD;IAC5E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAwC;IAC7E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAU;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6B;IAC1D,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAY;IAC/C,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAY;IAE5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAK3B;IAEF,OAAO,CAAC,gBAAgB;YAUV,gBAAgB;IAkB9B;;;OAGG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;gBAoBzF,MAAM,EAAE,oBAAoB,EAAE,OAAO,GAAE,qBAA0B;YAwB/D,kBAAkB;IAiBhC,OAAO,CAAC,sBAAsB;YAYhB,cAAc;YAwDd,0BAA0B;IA0CxC;;;;;OAKG;YACW,kBAAkB;YAmBlB,kBAAkB;IAI1B,KAAK;IA2WL,IAAI;IAWV,eAAe,IAAI,qBAAqB;IAIxC,SAAS,IAAI,eAAe;IAQ5B;;;;;;;OAOG;IACG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAgCzG,iBAAiB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM;IAS/C,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAoBtE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAuBvE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAoBzC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAa5C,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;IAU9C,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI;CAGlD"}
@@ -213,23 +213,33 @@ export class SlackConnector {
213
213
  return [];
214
214
  }
215
215
  }
216
- async resolveChannelName(channelId) {
216
+ /**
217
+ * Resolve channel name and external-shared status in one cached call.
218
+ * `isExtShared` is true for Slack Connect (externally/org shared) channels —
219
+ * the delivery layer treats these (and unknowns) as "external" so the model's
220
+ * operator-facing notes are never posted there. See reply-disposition.ts.
221
+ */
222
+ async resolveChannelInfo(channelId) {
217
223
  const cached = this.channelNameCache.get(channelId);
218
224
  if (cached && Date.now() - cached.cachedAt < SlackConnector.CHANNEL_CACHE_TTL_MS) {
219
- return cached.name;
225
+ return { name: cached.name, isExtShared: cached.isExtShared };
220
226
  }
221
227
  try {
222
228
  const result = await this.app.client.conversations.info({ channel: channelId });
223
- const name = result.channel?.name;
224
- if (name) {
225
- this.channelNameCache.set(channelId, { name, cachedAt: Date.now() });
226
- return name;
227
- }
229
+ const channel = result.channel;
230
+ const name = channel?.name;
231
+ const isExtShared = !!(channel?.is_ext_shared || channel?.is_org_shared || channel?.is_shared);
232
+ this.channelNameCache.set(channelId, { name, isExtShared, cachedAt: Date.now() });
233
+ return { name, isExtShared };
228
234
  }
229
235
  catch (err) {
230
- logger.debug(`Failed to resolve channel name for ${channelId}: ${err}`);
236
+ logger.debug(`Failed to resolve channel info for ${channelId}: ${err}`);
231
237
  }
232
- return undefined;
238
+ // Unknown → treat as external (safe): never assume a channel is private.
239
+ return { name: undefined, isExtShared: true };
240
+ }
241
+ async resolveChannelName(channelId) {
242
+ return (await this.resolveChannelInfo(channelId)).name;
233
243
  }
234
244
  async start() {
235
245
  this.app.message(async ({ event }) => {
@@ -297,11 +307,14 @@ export class SlackConnector {
297
307
  }
298
308
  }
299
309
  const slackUserId = event.user;
300
- const [channelName, speaker] = await Promise.all([
301
- this.resolveChannelName(event.channel),
310
+ const [channelInfo, speaker] = await Promise.all([
311
+ this.resolveChannelInfo(event.channel),
302
312
  this.resolveSpeakerInfo(slackUserId),
303
313
  ]);
314
+ const channelName = channelInfo.name;
304
315
  const channelType = event.channel_type || "channel";
316
+ // DM is never "external"; otherwise trust the Slack Connect flag.
317
+ const channelExternal = channelType !== "im" && channelInfo.isExtShared;
305
318
  const rawText = (event.text || "");
306
319
  const wasMentioned = !!this.botUserId && rawText.includes(`<@${this.botUserId}>`);
307
320
  // Early silent for cross-bot / cross-user traffic in shared channels.
@@ -338,6 +351,7 @@ export class SlackConnector {
338
351
  raw: event,
339
352
  transportMeta: {
340
353
  channelType,
354
+ channelExternal,
341
355
  team: event.team || null,
342
356
  channelName: channelName || null,
343
357
  wasMentioned,
@@ -512,11 +526,12 @@ export class SlackConnector {
512
526
  logger.debug(`[slack] Reacted-to message has no text, skipping`);
513
527
  return;
514
528
  }
515
- // Resolve channel name and reactor (speaker) in parallel
516
- const [channelName, speaker] = await Promise.all([
517
- this.resolveChannelName(channelId),
529
+ // Resolve channel name/external status and reactor (speaker) in parallel
530
+ const [channelInfo, speaker] = await Promise.all([
531
+ this.resolveChannelInfo(channelId),
518
532
  this.resolveSpeakerInfo(event.user),
519
533
  ]);
534
+ const channelName = channelInfo.name;
520
535
  const channelDisplay = channelName ? `#${channelName}` : channelId;
521
536
  // Build the prompt with reaction context
522
537
  const prompt = `[Reaction :${emoji}: on message in ${channelDisplay}]\n\nOriginal message:\n"${messageText}"\n\nThe user reacted with :${emoji}: to this message. Interpret and act on the reaction.`;
@@ -540,6 +555,7 @@ export class SlackConnector {
540
555
  raw: event,
541
556
  transportMeta: {
542
557
  channelType: "channel",
558
+ channelExternal: channelInfo.isExtShared,
543
559
  team: null,
544
560
  channelName: channelName || null,
545
561
  ...this.speakerTransportFields(speaker, event.user),