@agent-assistant/surfaces 0.1.4 → 0.2.0
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.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/slack-thread-gate.d.ts +52 -0
- package/dist/slack-thread-gate.js +49 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { createSurfaceRegistry } from './surfaces.js';
|
|
2
2
|
export { SurfaceConflictError, SurfaceDeliveryError, SurfaceNotFoundError, } from './types.js';
|
|
3
3
|
export type { FanoutOutcome, FanoutPolicy, FanoutResult, NormalizedInboundMessage, SurfaceAdapter, SurfaceCapabilities, SurfaceConnection, SurfaceFormatHook, SurfaceOutboundEvent, SurfacePayload, SurfaceRegistry, SurfaceRegistryConfig, SurfaceState, SurfaceType, } from './types.js';
|
|
4
|
+
export { SlackThreadGate } from "./slack-thread-gate.js";
|
|
5
|
+
export type { ActiveThreadContext, ActiveThreadKey, ActiveThreadStore, SlackThreadGateOptions, ThreadGateDecision, ThreadGateDropReason, ThreadGateEvent, } from "./slack-thread-gate.js";
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SlackThreadGate — decides whether an agent should respond to an inbound Slack event.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* 1. @-mentions always proceed, and the thread is marked active so follow-up replies
|
|
6
|
+
* in the same thread are also processed.
|
|
7
|
+
* 2. Thread replies (non-mention message events with a thread_ts) only proceed if
|
|
8
|
+
* the thread was previously engaged by a mention. Otherwise they are dropped.
|
|
9
|
+
* 3. Non-thread message events (no thread_ts) proceed — callers may gate further.
|
|
10
|
+
*
|
|
11
|
+
* Active-thread state is keyed by the composite {workspaceId, channel, threadTs}.
|
|
12
|
+
* Slack's `thread_ts` is only unique within a channel — across workspaces or channels
|
|
13
|
+
* a bare-ts lookup would let a mention in one channel unlock unrelated threads in
|
|
14
|
+
* another. The store is caller-supplied (Cloudflare KV, Redis, in-memory, etc.) so
|
|
15
|
+
* the gate is runtime-agnostic; callers join the key fields into their native key
|
|
16
|
+
* format (e.g. `${workspaceId}:${channel}:${threadTs}`).
|
|
17
|
+
*/
|
|
18
|
+
export interface ActiveThreadContext {
|
|
19
|
+
workspaceId: string;
|
|
20
|
+
channel: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ActiveThreadKey extends ActiveThreadContext {
|
|
23
|
+
threadTs: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ActiveThreadStore {
|
|
26
|
+
isActive(key: ActiveThreadKey): Promise<boolean>;
|
|
27
|
+
markActive(key: ActiveThreadKey, ttlSeconds: number): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface ThreadGateEvent {
|
|
30
|
+
type: "mention" | "message" | string;
|
|
31
|
+
channel: string;
|
|
32
|
+
threadTs?: string;
|
|
33
|
+
ts?: string;
|
|
34
|
+
}
|
|
35
|
+
export type ThreadGateDropReason = "inactive-thread";
|
|
36
|
+
export interface ThreadGateDecision {
|
|
37
|
+
proceed: boolean;
|
|
38
|
+
reason?: ThreadGateDropReason;
|
|
39
|
+
}
|
|
40
|
+
export interface SlackThreadGateOptions {
|
|
41
|
+
store: ActiveThreadStore;
|
|
42
|
+
/** Active-thread TTL. Defaults to 86_400 (24h). */
|
|
43
|
+
ttlSeconds?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare class SlackThreadGate {
|
|
46
|
+
private readonly store;
|
|
47
|
+
private readonly ttlSeconds;
|
|
48
|
+
constructor(options: SlackThreadGateOptions);
|
|
49
|
+
shouldProcess(event: ThreadGateEvent, workspaceId: string): Promise<ThreadGateDecision>;
|
|
50
|
+
onEngaged(event: ThreadGateEvent, workspaceId: string): Promise<void>;
|
|
51
|
+
refresh(key: ActiveThreadKey): Promise<void>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SlackThreadGate — decides whether an agent should respond to an inbound Slack event.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* 1. @-mentions always proceed, and the thread is marked active so follow-up replies
|
|
6
|
+
* in the same thread are also processed.
|
|
7
|
+
* 2. Thread replies (non-mention message events with a thread_ts) only proceed if
|
|
8
|
+
* the thread was previously engaged by a mention. Otherwise they are dropped.
|
|
9
|
+
* 3. Non-thread message events (no thread_ts) proceed — callers may gate further.
|
|
10
|
+
*
|
|
11
|
+
* Active-thread state is keyed by the composite {workspaceId, channel, threadTs}.
|
|
12
|
+
* Slack's `thread_ts` is only unique within a channel — across workspaces or channels
|
|
13
|
+
* a bare-ts lookup would let a mention in one channel unlock unrelated threads in
|
|
14
|
+
* another. The store is caller-supplied (Cloudflare KV, Redis, in-memory, etc.) so
|
|
15
|
+
* the gate is runtime-agnostic; callers join the key fields into their native key
|
|
16
|
+
* format (e.g. `${workspaceId}:${channel}:${threadTs}`).
|
|
17
|
+
*/
|
|
18
|
+
export class SlackThreadGate {
|
|
19
|
+
store;
|
|
20
|
+
ttlSeconds;
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.store = options.store;
|
|
23
|
+
this.ttlSeconds = options.ttlSeconds ?? 86_400;
|
|
24
|
+
}
|
|
25
|
+
async shouldProcess(event, workspaceId) {
|
|
26
|
+
if (event.type === "message" && event.threadTs) {
|
|
27
|
+
const active = await this.store.isActive({
|
|
28
|
+
workspaceId,
|
|
29
|
+
channel: event.channel,
|
|
30
|
+
threadTs: event.threadTs,
|
|
31
|
+
});
|
|
32
|
+
if (!active) {
|
|
33
|
+
return { proceed: false, reason: "inactive-thread" };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { proceed: true };
|
|
37
|
+
}
|
|
38
|
+
async onEngaged(event, workspaceId) {
|
|
39
|
+
if (event.type !== "mention")
|
|
40
|
+
return;
|
|
41
|
+
const threadTs = event.threadTs ?? event.ts;
|
|
42
|
+
if (!threadTs)
|
|
43
|
+
return;
|
|
44
|
+
await this.store.markActive({ workspaceId, channel: event.channel, threadTs }, this.ttlSeconds);
|
|
45
|
+
}
|
|
46
|
+
async refresh(key) {
|
|
47
|
+
await this.store.markActive(key, this.ttlSeconds);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/package.json
CHANGED