@meetquinn/animactl 0.1.21 → 0.1.22-canary.563.1.0620c02
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/server/inbox/observed-conversation.d.ts +24 -0
- package/dist/server/inbox/observed-conversation.d.ts.map +1 -0
- package/dist/server/inbox/observed-conversation.js +69 -0
- package/dist/server/inbox/slack-events.d.ts +14 -0
- package/dist/server/inbox/slack-events.d.ts.map +1 -1
- package/dist/server/inbox/slack-events.js +18 -0
- package/dist/server/inbox/slack-subscriber.d.ts.map +1 -1
- package/dist/server/inbox/slack-subscriber.js +11 -1
- package/dist/server/storage/schema/observed-conversation.store.d.ts +212 -0
- package/dist/server/storage/schema/observed-conversation.store.d.ts.map +1 -0
- package/dist/server/storage/schema/observed-conversation.store.js +461 -0
- package/dist/server/tests/observed-conversation.test.d.ts +2 -0
- package/dist/server/tests/observed-conversation.test.d.ts.map +1 -0
- package/dist/server/tests/observed-conversation.test.js +680 -0
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type SlackRawFile } from '../slack/slack.helper.js';
|
|
2
|
+
import { type ObservedFileDescriptor, type ObserveSlackMessageResult } from '../storage/schema/observed-conversation.store.js';
|
|
3
|
+
import { type ObservableSlackMessage, type SlackMessageEnvelope, type SlackRawMessageEvent } from './slack-events.js';
|
|
4
|
+
export declare function observeObservableSlackMessage(input: {
|
|
5
|
+
agentId: string;
|
|
6
|
+
envelope?: SlackMessageEnvelope;
|
|
7
|
+
event: ObservableSlackMessage;
|
|
8
|
+
/** When true, rethrow store failures after marking continuity degraded. Default: mark degraded and continue. */
|
|
9
|
+
throwOnError?: boolean;
|
|
10
|
+
}): Promise<ObserveSlackMessageResult | undefined>;
|
|
11
|
+
/**
|
|
12
|
+
* Ingress entry: if the raw event is observable, journal it (fail-soft by
|
|
13
|
+
* default). Returns whether observation was attempted.
|
|
14
|
+
*/
|
|
15
|
+
export declare function observeSlackEventAtIngress(input: {
|
|
16
|
+
agentId: string;
|
|
17
|
+
envelope?: SlackMessageEnvelope;
|
|
18
|
+
event: SlackRawMessageEvent;
|
|
19
|
+
throwOnError?: boolean;
|
|
20
|
+
}): Promise<ObserveSlackMessageResult | undefined>;
|
|
21
|
+
/** @deprecated Use observeObservableSlackMessage / observeSlackEventAtIngress. */
|
|
22
|
+
export declare const observeRoutableSlackMessage: typeof observeObservableSlackMessage;
|
|
23
|
+
export declare function observedFilesFromSlack(files: SlackRawFile[] | undefined): ObservedFileDescriptor[];
|
|
24
|
+
//# sourceMappingURL=observed-conversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observed-conversation.d.ts","sourceRoot":"","sources":["../../../server/inbox/observed-conversation.ts"],"names":[],"mappings":"AAOA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC/B,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,6BAA6B,CAAC,KAAK,EAAE;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,sBAAsB,CAAC;IAC9B,gHAAgH;IAChH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAwBjD;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,KAAK,EAAE,oBAAoB,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAQjD;AAED,kFAAkF;AAClF,eAAO,MAAM,2BAA2B,sCAAgC,CAAC;AAEzE,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EAAE,GAAG,SAAS,GAChC,sBAAsB,EAAE,CAc1B"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Slack ingress helper: record an observable message into the agent-scoped
|
|
2
|
+
// observed-conversation journal before wake/subscription filtering.
|
|
3
|
+
//
|
|
4
|
+
// Observation uses a broader predicate than runtime routability: userless
|
|
5
|
+
// bot_message and file-only posts are journaled without changing wake routing.
|
|
6
|
+
import { errorMessage } from '../ids.js';
|
|
7
|
+
import { slackTsToIso } from '../slack/slack.helper.js';
|
|
8
|
+
import { observedConversationStoreForAgent, } from '../storage/schema/observed-conversation.store.js';
|
|
9
|
+
import { isObservableSlackMessage, slackEventTeamId, } from './slack-events.js';
|
|
10
|
+
export async function observeObservableSlackMessage(input) {
|
|
11
|
+
const teamId = slackEventTeamId(input.envelope, input.event);
|
|
12
|
+
const store = observedConversationStoreForAgent(input.agentId);
|
|
13
|
+
const files = observedFilesFromSlack(input.event.files);
|
|
14
|
+
try {
|
|
15
|
+
return await store.observe({
|
|
16
|
+
...(input.event.bot_id ? { botId: input.event.bot_id } : {}),
|
|
17
|
+
channelId: input.event.channel,
|
|
18
|
+
...(files.length > 0 ? { files } : {}),
|
|
19
|
+
messageTs: input.event.ts,
|
|
20
|
+
...(slackTsToIso(input.event.ts) ? { receivedAt: slackTsToIso(input.event.ts) } : {}),
|
|
21
|
+
teamId,
|
|
22
|
+
text: input.event.text ?? '',
|
|
23
|
+
...(input.event.thread_ts ? { threadTs: input.event.thread_ts } : {}),
|
|
24
|
+
...(input.event.user ? { userId: input.event.user } : {}),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
// store.observe already marks continuity degraded; helper only controls throw.
|
|
29
|
+
if (input.throwOnError)
|
|
30
|
+
throw error;
|
|
31
|
+
console.warn(`observed-conversation journal write failed for agent=${input.agentId} channel=${input.event.channel} ts=${input.event.ts}: ${errorMessage(error)}`);
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Ingress entry: if the raw event is observable, journal it (fail-soft by
|
|
37
|
+
* default). Returns whether observation was attempted.
|
|
38
|
+
*/
|
|
39
|
+
export async function observeSlackEventAtIngress(input) {
|
|
40
|
+
if (!isObservableSlackMessage(input.event))
|
|
41
|
+
return undefined;
|
|
42
|
+
return observeObservableSlackMessage({
|
|
43
|
+
agentId: input.agentId,
|
|
44
|
+
envelope: input.envelope,
|
|
45
|
+
event: input.event,
|
|
46
|
+
...(input.throwOnError ? { throwOnError: true } : {}),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** @deprecated Use observeObservableSlackMessage / observeSlackEventAtIngress. */
|
|
50
|
+
export const observeRoutableSlackMessage = observeObservableSlackMessage;
|
|
51
|
+
export function observedFilesFromSlack(files) {
|
|
52
|
+
if (!Array.isArray(files) || files.length === 0)
|
|
53
|
+
return [];
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
if (!file || typeof file.id !== 'string' || file.id.trim().length === 0)
|
|
57
|
+
continue;
|
|
58
|
+
const descriptor = { id: file.id };
|
|
59
|
+
if (typeof file.name === 'string' && file.name.length > 0)
|
|
60
|
+
descriptor.name = file.name;
|
|
61
|
+
else if (typeof file.title === 'string' && file.title.length > 0)
|
|
62
|
+
descriptor.name = file.title;
|
|
63
|
+
if (typeof file.mimetype === 'string' && file.mimetype.length > 0) {
|
|
64
|
+
descriptor.mimetype = file.mimetype;
|
|
65
|
+
}
|
|
66
|
+
out.push(descriptor);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
@@ -35,6 +35,20 @@ export interface RoutableSlackMessage extends SlackRawMessageEvent {
|
|
|
35
35
|
type: 'app_mention' | 'message';
|
|
36
36
|
user: string;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Observable for the local observed-conversation journal — broader than runtime
|
|
40
|
+
* routability. Captures userless `bot_message` (bot_id only) and file-only posts
|
|
41
|
+
* without requiring a human `user`. Wake routing still uses isRoutableSlackMessage.
|
|
42
|
+
*/
|
|
43
|
+
export interface ObservableSlackMessage extends SlackRawMessageEvent {
|
|
44
|
+
channel: string;
|
|
45
|
+
ts: string;
|
|
46
|
+
type: 'app_mention' | 'message';
|
|
47
|
+
/** At least one of user / bot_id is present (enforced by the predicate). */
|
|
48
|
+
bot_id?: string;
|
|
49
|
+
user?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function isObservableSlackMessage(event: SlackRawMessageEvent): event is ObservableSlackMessage;
|
|
38
52
|
export declare function isRoutableSlackMessage(event: SlackRawMessageEvent): event is RoutableSlackMessage;
|
|
39
53
|
export declare function slackEventTeamId(envelope: SlackMessageEnvelope | undefined, event: SlackRawMessageEvent): string;
|
|
40
54
|
export declare function normalizeSlackMessage(input: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-events.d.ts","sourceRoot":"","sources":["../../../server/inbox/slack-events.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGrG,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,SAAS,GAAG,IAAI,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,aAAa,GAAG,SAAS,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,oBAAoB,GAAG,KAAK,IAAI,oBAAoB,CAUjG;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,oBAAoB,GAAG,SAAS,EAC1C,KAAK,EAAE,oBAAoB,GAC1B,MAAM,CAER;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,oBAAoB,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC,GAAG,cAAc,CA2BjB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAEpE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAEpE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,CAcxE"}
|
|
1
|
+
{"version":3,"file":"slack-events.d.ts","sourceRoot":"","sources":["../../../server/inbox/slack-events.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGrG,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,SAAS,GAAG,IAAI,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,SAAS,GAAG,QAAQ,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,aAAa,GAAG,SAAS,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAuB,SAAQ,oBAAoB;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,aAAa,GAAG,SAAS,CAAC;IAChC,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,oBAAoB,GAAG,KAAK,IAAI,sBAAsB,CAcrG;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,oBAAoB,GAAG,KAAK,IAAI,oBAAoB,CAUjG;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,oBAAoB,GAAG,SAAS,EAC1C,KAAK,EAAE,oBAAoB,GAC1B,MAAM,CAER;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,oBAAoB,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC,GAAG,cAAc,CA2BjB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAEpE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAEpE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,CAcxE"}
|
|
@@ -2,6 +2,24 @@ import { nowIso, slackMessageEventId, slackSurfaceId } from '../ids.js';
|
|
|
2
2
|
import { normalizeSlackEventFiles, slackTsToIso } from '../slack/slack.helper.js';
|
|
3
3
|
import { slackMessagePreviewsFromAttachments } from '../slack/message-previews.js';
|
|
4
4
|
import { slackVisibleMessageText } from '../slack/message-text.js';
|
|
5
|
+
export function isObservableSlackMessage(event) {
|
|
6
|
+
if (event.type !== 'message' && event.type !== 'app_mention')
|
|
7
|
+
return false;
|
|
8
|
+
if (typeof event.channel !== 'string' || typeof event.ts !== 'string')
|
|
9
|
+
return false;
|
|
10
|
+
if (event.subtype !== undefined && event.subtype !== 'bot_message' && event.subtype !== 'file_share') {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const hasUser = typeof event.user === 'string' && event.user.length > 0;
|
|
14
|
+
const hasBot = typeof event.bot_id === 'string' && event.bot_id.length > 0;
|
|
15
|
+
if (!hasUser && !hasBot)
|
|
16
|
+
return false;
|
|
17
|
+
// text may be missing on bot_message; empty + files still counts.
|
|
18
|
+
const text = typeof event.text === 'string' ? event.text : '';
|
|
19
|
+
const hasText = text.trim().length > 0;
|
|
20
|
+
const hasFiles = Array.isArray(event.files) && event.files.length > 0;
|
|
21
|
+
return hasText || hasFiles;
|
|
22
|
+
}
|
|
5
23
|
export function isRoutableSlackMessage(event) {
|
|
6
24
|
return ((event.type === 'message' || event.type === 'app_mention') &&
|
|
7
25
|
typeof event.channel === 'string' &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack-subscriber.d.ts","sourceRoot":"","sources":["../../../server/inbox/slack-subscriber.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"slack-subscriber.d.ts","sourceRoot":"","sources":["../../../server/inbox/slack-subscriber.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAoC5D,OAAO,EAAE,gBAAgB,EAA+B,MAAM,yBAAyB,CAAC;AAExF,MAAM,WAAW,2BAA2B;IAC1C,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,uGAAuG;IACvG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,qBAAa,oBAAoB;IAQnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAPpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAuB;IACvD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA8B;IAG5D,OAAO,CAAC,0BAA0B,CAAS;gBAEd,OAAO,EAAE,2BAA2B;IAO3D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAItB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,OAAO,CAAC,SAAS;YAgFH,oBAAoB;YASpB,qBAAqB;YAOrB,0BAA0B;YA2C1B,kCAAkC;YAYlC,gBAAgB;IA2E9B,OAAO,CAAC,uBAAuB;CAYhC;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE;IACvD,IAAI,EAAE,cAAc,CAAC;IACrB,WAAW,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAC3E,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;CACpD,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB"}
|
|
@@ -13,6 +13,7 @@ import { withCanonicalSlackVisibleText } from '../slack/message-text.js';
|
|
|
13
13
|
import { isSlackEvent, isRoutableSlackMessage, slackEventTeamId, slackSurfaceForEvent, } from './slack-events.js';
|
|
14
14
|
import { slackAttentionSuggestionPayload, } from './attention-suggestion-activity.js';
|
|
15
15
|
import { runIngestPipeline } from './ingest-pipeline.js';
|
|
16
|
+
import { observeSlackEventAtIngress } from './observed-conversation.js';
|
|
16
17
|
import { buildSlackInboxItemWithLatePreview } from './slack-ingest.js';
|
|
17
18
|
import { slackShortcutHandoffServiceForAgent } from './slack-shortcut-handoff.service.js';
|
|
18
19
|
import { slackRuntimeDecision } from './slack-subscription.service.js';
|
|
@@ -192,9 +193,18 @@ export class SlackInboxSubscriber {
|
|
|
192
193
|
// `text` can truncate before a trailing user mention; enrichment after an
|
|
193
194
|
// ignore never runs, so mention detection must see the full body first.
|
|
194
195
|
const rawEvent = withCanonicalSlackVisibleText(event);
|
|
196
|
+
const envelope = body;
|
|
197
|
+
// Observed-conversation journal (send-hold cut a): record every observable
|
|
198
|
+
// Slack message this agent process sees — including userless bot_message —
|
|
199
|
+
// BEFORE wake/subscription filtering and BEFORE isRoutableSlackMessage.
|
|
200
|
+
// Failures mark continuity degraded and never block routing.
|
|
201
|
+
await observeSlackEventAtIngress({
|
|
202
|
+
agentId: this.options.queue.agentId,
|
|
203
|
+
envelope,
|
|
204
|
+
event: rawEvent,
|
|
205
|
+
});
|
|
195
206
|
if (!isRoutableSlackMessage(rawEvent))
|
|
196
207
|
return;
|
|
197
|
-
const envelope = body;
|
|
198
208
|
const teamId = slackEventTeamId(envelope, rawEvent);
|
|
199
209
|
let latePreview;
|
|
200
210
|
await runIngestPipeline({
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Bound recent dedupe window inside one conversation journal. */
|
|
3
|
+
export declare const OBSERVED_CONVERSATION_DEDUPE_RECENT = 2000;
|
|
4
|
+
/** Cap retained journal archives per conversation (plus the live segment). */
|
|
5
|
+
export declare const OBSERVED_CONVERSATION_MAX_ARCHIVES = 4;
|
|
6
|
+
/** Minimal local file descriptor for file-only / file-bearing messages. */
|
|
7
|
+
export declare const ObservedFileDescriptorSchema: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
mimetype: z.ZodOptional<z.ZodString>;
|
|
10
|
+
name: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type ObservedFileDescriptor = z.infer<typeof ObservedFileDescriptorSchema>;
|
|
13
|
+
export declare const ObservedConversationEntrySchema: z.ZodObject<{
|
|
14
|
+
botId: z.ZodOptional<z.ZodString>;
|
|
15
|
+
channelId: z.ZodString;
|
|
16
|
+
eventId: z.ZodString;
|
|
17
|
+
files: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
18
|
+
id: z.ZodString;
|
|
19
|
+
mimetype: z.ZodOptional<z.ZodString>;
|
|
20
|
+
name: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strip>>>;
|
|
22
|
+
messageTs: z.ZodString;
|
|
23
|
+
observedAt: z.ZodString;
|
|
24
|
+
ordinal: z.ZodNumber;
|
|
25
|
+
receivedAt: z.ZodString;
|
|
26
|
+
surfaceId: z.ZodString;
|
|
27
|
+
teamId: z.ZodString;
|
|
28
|
+
text: z.ZodString;
|
|
29
|
+
threadTs: z.ZodOptional<z.ZodString>;
|
|
30
|
+
userId: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export type ObservedConversationEntry = z.infer<typeof ObservedConversationEntrySchema>;
|
|
33
|
+
/** Journal-tail metadata for one conversation (not the delivered cursor). */
|
|
34
|
+
export declare const ConversationIndexSchema: z.ZodObject<{
|
|
35
|
+
lastEventId: z.ZodString;
|
|
36
|
+
lastMessageTs: z.ZodString;
|
|
37
|
+
surfaceId: z.ZodString;
|
|
38
|
+
tailOrdinal: z.ZodNumber;
|
|
39
|
+
updatedAt: z.ZodString;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
export type ConversationIndex = z.infer<typeof ConversationIndexSchema>;
|
|
42
|
+
/**
|
|
43
|
+
* Delivered cursor record.
|
|
44
|
+
*
|
|
45
|
+
* - `deliveredOrdinal: null` → **confirmed absent** (never delivered into this
|
|
46
|
+
* conversation; "no-cursor lands" applies only to this state).
|
|
47
|
+
* - `deliveredOrdinal: 0` → present at origin (established, nothing past start).
|
|
48
|
+
* - `deliveredOrdinal: N>0` → delivered through ordinal N inclusive.
|
|
49
|
+
*
|
|
50
|
+
* A missing cursor file reads as confirmed absent via empty().
|
|
51
|
+
*/
|
|
52
|
+
export declare const ConversationCursorRecordSchema: z.ZodObject<{
|
|
53
|
+
deliveredOrdinal: z.ZodNullable<z.ZodNumber>;
|
|
54
|
+
lastDeliveredEventId: z.ZodOptional<z.ZodString>;
|
|
55
|
+
lastDeliveredMessageTs: z.ZodOptional<z.ZodString>;
|
|
56
|
+
surfaceId: z.ZodString;
|
|
57
|
+
updatedAt: z.ZodString;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
export type ConversationCursorRecord = z.infer<typeof ConversationCursorRecordSchema>;
|
|
60
|
+
export type ConversationCursorView = {
|
|
61
|
+
status: 'absent';
|
|
62
|
+
surfaceId: string;
|
|
63
|
+
} | {
|
|
64
|
+
status: 'present';
|
|
65
|
+
deliveredOrdinal: number;
|
|
66
|
+
lastDeliveredEventId?: string;
|
|
67
|
+
lastDeliveredMessageTs?: string;
|
|
68
|
+
surfaceId: string;
|
|
69
|
+
updatedAt: string;
|
|
70
|
+
};
|
|
71
|
+
export type AdvanceCursorExpected = {
|
|
72
|
+
status: 'absent';
|
|
73
|
+
} | {
|
|
74
|
+
status: 'present';
|
|
75
|
+
deliveredOrdinal: number;
|
|
76
|
+
};
|
|
77
|
+
export type AdvanceCursorResult = {
|
|
78
|
+
advanced: true;
|
|
79
|
+
cursor: Extract<ConversationCursorView, {
|
|
80
|
+
status: 'present';
|
|
81
|
+
}>;
|
|
82
|
+
} | {
|
|
83
|
+
advanced: false;
|
|
84
|
+
reason: 'cas_mismatch' | 'regression' | 'beyond_tail';
|
|
85
|
+
cursor: ConversationCursorView;
|
|
86
|
+
/** Reconciled journal tail when reason is beyond_tail (0 if no observations). */
|
|
87
|
+
tailOrdinal?: number;
|
|
88
|
+
};
|
|
89
|
+
/** Agent-wide observation continuity (fail-closed signal for later send hold). */
|
|
90
|
+
export declare const ObservationContinuitySchema: z.ZodObject<{
|
|
91
|
+
lastFailureAt: z.ZodOptional<z.ZodString>;
|
|
92
|
+
lastFailureEventId: z.ZodOptional<z.ZodString>;
|
|
93
|
+
lastFailureMessage: z.ZodOptional<z.ZodString>;
|
|
94
|
+
lastFailureSurfaceId: z.ZodOptional<z.ZodString>;
|
|
95
|
+
lastSuccessAt: z.ZodOptional<z.ZodString>;
|
|
96
|
+
status: z.ZodEnum<{
|
|
97
|
+
ok: "ok";
|
|
98
|
+
degraded: "degraded";
|
|
99
|
+
}>;
|
|
100
|
+
updatedAt: z.ZodString;
|
|
101
|
+
}, z.core.$strip>;
|
|
102
|
+
export type ObservationContinuity = z.infer<typeof ObservationContinuitySchema>;
|
|
103
|
+
export interface ObserveSlackMessageInput {
|
|
104
|
+
botId?: string;
|
|
105
|
+
channelId: string;
|
|
106
|
+
files?: ObservedFileDescriptor[];
|
|
107
|
+
messageTs: string;
|
|
108
|
+
/** ISO time derived from Slack ts when available. */
|
|
109
|
+
receivedAt?: string;
|
|
110
|
+
teamId: string;
|
|
111
|
+
text: string;
|
|
112
|
+
/** Thread parent ts for replies only; omit for top-level / DM root. */
|
|
113
|
+
threadTs?: string;
|
|
114
|
+
userId?: string;
|
|
115
|
+
}
|
|
116
|
+
export type ObserveSlackMessageResult = {
|
|
117
|
+
appended: true;
|
|
118
|
+
entry: ObservedConversationEntry;
|
|
119
|
+
} | {
|
|
120
|
+
appended: false;
|
|
121
|
+
reason: 'duplicate';
|
|
122
|
+
entry?: ObservedConversationEntry;
|
|
123
|
+
};
|
|
124
|
+
export declare function observedConversationsDir(agentId: string): string;
|
|
125
|
+
/** Stable filesystem key for a surface id (no path separators / nulls). */
|
|
126
|
+
export declare function observedConversationFileStem(surfaceId: string): string;
|
|
127
|
+
/**
|
|
128
|
+
* Explicit top-level vs thread partition: only true replies (thread_ts set and
|
|
129
|
+
* different from the message ts) join a thread surface. Channel parents and
|
|
130
|
+
* DM roots stay on the unthreaded surface.
|
|
131
|
+
*/
|
|
132
|
+
export declare function conversationThreadTs(input: {
|
|
133
|
+
messageTs: string;
|
|
134
|
+
threadTs?: string;
|
|
135
|
+
}): string | undefined;
|
|
136
|
+
export declare function surfaceIdForObservation(input: {
|
|
137
|
+
channelId: string;
|
|
138
|
+
messageTs: string;
|
|
139
|
+
teamId: string;
|
|
140
|
+
threadTs?: string;
|
|
141
|
+
}): string;
|
|
142
|
+
export declare function cursorViewFromRecord(surfaceId: string, record: ConversationCursorRecord): ConversationCursorView;
|
|
143
|
+
/**
|
|
144
|
+
* Rebuild journal-tail index from retained journal rows when the index lags
|
|
145
|
+
* (append-then-index-write crash recovery).
|
|
146
|
+
*/
|
|
147
|
+
export declare function reconcileIndexFromJournal(current: ConversationIndex, recent: ObservedConversationEntry[], surfaceId: string): ConversationIndex;
|
|
148
|
+
export interface ObservedConversationStoreOptions {
|
|
149
|
+
/** Journal rotation size; tests may pass a tiny value. */
|
|
150
|
+
maxBytes?: number;
|
|
151
|
+
maxArchives?: number;
|
|
152
|
+
}
|
|
153
|
+
export declare class ObservedConversationStore {
|
|
154
|
+
private readonly agentId;
|
|
155
|
+
private readonly options;
|
|
156
|
+
constructor(agentId: string, options?: ObservedConversationStoreOptions);
|
|
157
|
+
/**
|
|
158
|
+
* Record one Slack message observation. Idempotent on eventId within the
|
|
159
|
+
* conversation. Reconciles a stale index from the journal before assigning
|
|
160
|
+
* a new ordinal (crash recovery).
|
|
161
|
+
*/
|
|
162
|
+
observe(input: ObserveSlackMessageInput): Promise<ObserveSlackMessageResult>;
|
|
163
|
+
getIndex(surfaceId: string): Promise<ConversationIndex | undefined>;
|
|
164
|
+
/**
|
|
165
|
+
* Read journal-tail index, reconciling from the retained journal when the
|
|
166
|
+
* stored index lags (same recovery path as observe).
|
|
167
|
+
*/
|
|
168
|
+
getIndexReconciled(surfaceId: string): Promise<ConversationIndex | undefined>;
|
|
169
|
+
readJournal(surfaceId: string, options?: {
|
|
170
|
+
afterOrdinal?: number;
|
|
171
|
+
limit?: number;
|
|
172
|
+
}): Promise<ObservedConversationEntry[]>;
|
|
173
|
+
readTail(surfaceId: string, limit: number): Promise<ObservedConversationEntry[]>;
|
|
174
|
+
getCursor(surfaceId: string): Promise<ConversationCursorView>;
|
|
175
|
+
/**
|
|
176
|
+
* Monotonic CAS advance of the delivered cursor.
|
|
177
|
+
*
|
|
178
|
+
* - `expected` must match the current absent/present+ordinal view.
|
|
179
|
+
* - `nextDeliveredOrdinal` must be >= 0; if current is present, next must be
|
|
180
|
+
* >= current.deliveredOrdinal (regression otherwise).
|
|
181
|
+
* - Fail-closed against the journal: next must be <= reconciled journal tail.
|
|
182
|
+
* The only allowed advance with no observed rows is present@0 (thread-root
|
|
183
|
+
* establishment). Anything above tail returns `beyond_tail`.
|
|
184
|
+
*/
|
|
185
|
+
advanceCursor(input: {
|
|
186
|
+
expected: AdvanceCursorExpected;
|
|
187
|
+
lastDeliveredEventId?: string;
|
|
188
|
+
lastDeliveredMessageTs?: string;
|
|
189
|
+
nextDeliveredOrdinal: number;
|
|
190
|
+
surfaceId: string;
|
|
191
|
+
}): Promise<AdvanceCursorResult>;
|
|
192
|
+
getContinuity(): Promise<ObservationContinuity>;
|
|
193
|
+
markDegraded(input: {
|
|
194
|
+
eventId?: string;
|
|
195
|
+
message: string;
|
|
196
|
+
surfaceId?: string;
|
|
197
|
+
}): Promise<ObservationContinuity>;
|
|
198
|
+
/** Best-effort: never mask the observation error with a continuity write error. */
|
|
199
|
+
private markDegradedSafe;
|
|
200
|
+
/** Test/recovery helper: force-write journal-tail index without observing. */
|
|
201
|
+
writeIndexForTest(index: ConversationIndex): Promise<void>;
|
|
202
|
+
private journal;
|
|
203
|
+
private indexStore;
|
|
204
|
+
private cursorStore;
|
|
205
|
+
private continuityStore;
|
|
206
|
+
private journalPath;
|
|
207
|
+
private indexPath;
|
|
208
|
+
private cursorPath;
|
|
209
|
+
private continuityPath;
|
|
210
|
+
}
|
|
211
|
+
export declare function observedConversationStoreForAgent(agentId: string): ObservedConversationStore;
|
|
212
|
+
//# sourceMappingURL=observed-conversation.store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observed-conversation.store.d.ts","sourceRoot":"","sources":["../../../../server/storage/schema/observed-conversation.store.ts"],"names":[],"mappings":"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"}
|