@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.
@@ -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;AAmC5D,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;IAgE9B,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"}
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":"AAoBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,kEAAkE;AAClE,eAAO,MAAM,mCAAmC,OAAQ,CAAC;AAEzD,8EAA8E;AAC9E,eAAO,MAAM,kCAAkC,IAAI,CAAC;AAEpD,2EAA2E;AAC3E,eAAO,MAAM,4BAA4B;;;;iBAIvC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;iBAmBxC,CAAC;AAEL,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB;;;;;;iBAMlC,CAAC;AAEH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,8BAA8B;;;;;;iBAMzC,CAAC;AAEH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACvC;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEN,MAAM,MAAM,qBAAqB,GAC7B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,EAAE;QAAE,MAAM,EAAE,SAAS,CAAA;KAAE,CAAC,CAAA;CAAE,GAClF;IACE,QAAQ,EAAE,KAAK,CAAC;IAChB,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,aAAa,CAAC;IACtD,MAAM,EAAE,sBAAsB,CAAC;IAC/B,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEN,kFAAkF;AAClF,eAAO,MAAM,2BAA2B;;;;;;;;;;;iBAQtC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,yBAAyB,GACjC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,yBAAyB,CAAA;CAAE,GACpD;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,CAAC,EAAE,yBAAyB,CAAA;CAAE,CAAC;AAEhF,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,2EAA2E;AAC3E,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,GAAG,SAAS,CAKrB;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,MAAM,CAUT;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,wBAAwB,GAC/B,sBAAsB,CAcxB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,yBAAyB,EAAE,EACnC,SAAS,EAAE,MAAM,GAChB,iBAAiB,CAcnB;AAED,MAAM,WAAW,gCAAgC;IAC/C,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,yBAAyB;IAElC,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gCAAqC;IAGjE;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAyF5E,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAMzE;;;OAGG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAY7E,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAO,GACtD,OAAO,CAAC,yBAAyB,EAAE,CAAC;IAWjC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,yBAAyB,EAAE,CAAC;IAOhF,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAKnE;;;;;;;;;OASG;IACG,aAAa,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,qBAAqB,CAAC;QAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC,oBAAoB,EAAE,MAAM,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAgE1B,aAAa,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAQ/C,YAAY,CAAC,KAAK,EAAE;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAalC,mFAAmF;YACrE,gBAAgB;IAgB9B,8EAA8E;IACxE,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhE,OAAO,CAAC,OAAO;IAUf,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,cAAc;CAKvB;AAUD,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,GAAG,yBAAyB,CAE5F"}