crewly 1.20.149 → 1.20.150

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 (129) hide show
  1. package/dist/backend/backend/src/constants.d.ts +76 -0
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +77 -0
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts +1 -0
  6. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
  7. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +22 -0
  8. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
  9. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +1 -0
  10. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -1
  11. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +2 -0
  12. package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -1
  13. package/dist/backend/backend/src/controllers/messaging/messenger.routes.d.ts.map +1 -1
  14. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js +42 -1
  15. package/dist/backend/backend/src/controllers/messaging/messenger.routes.js.map +1 -1
  16. package/dist/backend/backend/src/index.d.ts +8 -0
  17. package/dist/backend/backend/src/index.d.ts.map +1 -1
  18. package/dist/backend/backend/src/index.js +62 -0
  19. package/dist/backend/backend/src/index.js.map +1 -1
  20. package/dist/backend/backend/src/services/chat-v2/chat-v2.relay-adapter.service.d.ts.map +1 -1
  21. package/dist/backend/backend/src/services/chat-v2/chat-v2.relay-adapter.service.js +3 -0
  22. package/dist/backend/backend/src/services/chat-v2/chat-v2.relay-adapter.service.js.map +1 -1
  23. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +94 -2
  24. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
  25. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +183 -2
  26. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
  27. package/dist/backend/backend/src/services/chat-v2/owner-inbound.utils.d.ts +70 -0
  28. package/dist/backend/backend/src/services/chat-v2/owner-inbound.utils.d.ts.map +1 -1
  29. package/dist/backend/backend/src/services/chat-v2/owner-inbound.utils.js +78 -0
  30. package/dist/backend/backend/src/services/chat-v2/owner-inbound.utils.js.map +1 -1
  31. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -1
  32. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js +9 -0
  33. package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -1
  34. package/dist/backend/backend/src/services/chat-v2/sqlite/cloud-outbox.store.d.ts +136 -0
  35. package/dist/backend/backend/src/services/chat-v2/sqlite/cloud-outbox.store.d.ts.map +1 -0
  36. package/dist/backend/backend/src/services/chat-v2/sqlite/cloud-outbox.store.js +207 -0
  37. package/dist/backend/backend/src/services/chat-v2/sqlite/cloud-outbox.store.js.map +1 -0
  38. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +62 -1
  39. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -1
  40. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +111 -4
  41. package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -1
  42. package/dist/backend/backend/src/services/chat-v2/sqlite/unified-log.d.ts +206 -0
  43. package/dist/backend/backend/src/services/chat-v2/sqlite/unified-log.d.ts.map +1 -0
  44. package/dist/backend/backend/src/services/chat-v2/sqlite/unified-log.js +442 -0
  45. package/dist/backend/backend/src/services/chat-v2/sqlite/unified-log.js.map +1 -0
  46. package/dist/backend/backend/src/services/chat-v2/types.d.ts +29 -0
  47. package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -1
  48. package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -1
  49. package/dist/backend/backend/src/services/cloud/conversation-cloud-sync.service.d.ts +209 -0
  50. package/dist/backend/backend/src/services/cloud/conversation-cloud-sync.service.d.ts.map +1 -0
  51. package/dist/backend/backend/src/services/cloud/conversation-cloud-sync.service.js +607 -0
  52. package/dist/backend/backend/src/services/cloud/conversation-cloud-sync.service.js.map +1 -0
  53. package/dist/backend/backend/src/services/cloud/conversation-ingest.contract.d.ts +167 -0
  54. package/dist/backend/backend/src/services/cloud/conversation-ingest.contract.d.ts.map +1 -0
  55. package/dist/backend/backend/src/services/cloud/conversation-ingest.contract.js +127 -0
  56. package/dist/backend/backend/src/services/cloud/conversation-ingest.contract.js.map +1 -0
  57. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +4 -0
  59. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  60. package/dist/backend/backend/src/services/messaging/google-chat-initializer.d.ts.map +1 -1
  61. package/dist/backend/backend/src/services/messaging/google-chat-initializer.js +11 -1
  62. package/dist/backend/backend/src/services/messaging/google-chat-initializer.js.map +1 -1
  63. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.d.ts +1 -1
  64. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
  65. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js +13 -0
  66. package/dist/backend/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
  67. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  68. package/dist/backend/backend/src/services/slack/slack-initializer.js +13 -0
  69. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  70. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +14 -1
  71. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +34 -2
  73. package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  74. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  75. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +1 -0
  76. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  77. package/dist/backend/backend/src/services/telegram/telegram-orchestrator-bridge.d.ts +10 -0
  78. package/dist/backend/backend/src/services/telegram/telegram-orchestrator-bridge.d.ts.map +1 -1
  79. package/dist/backend/backend/src/services/telegram/telegram-orchestrator-bridge.js +27 -2
  80. package/dist/backend/backend/src/services/telegram/telegram-orchestrator-bridge.js.map +1 -1
  81. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts +10 -0
  82. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -1
  83. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +27 -4
  84. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -1
  85. package/dist/backend/backend/src/types/slack.types.d.ts +12 -0
  86. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  87. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  88. package/dist/backend/build-info.json +2 -2
  89. package/dist/cli/backend/src/constants.d.ts +76 -0
  90. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  91. package/dist/cli/backend/src/constants.js +77 -0
  92. package/dist/cli/backend/src/constants.js.map +1 -1
  93. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +94 -2
  94. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
  95. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +183 -2
  96. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
  97. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -1
  98. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js +9 -0
  99. package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -1
  100. package/dist/cli/backend/src/services/chat-v2/sqlite/cloud-outbox.store.d.ts +136 -0
  101. package/dist/cli/backend/src/services/chat-v2/sqlite/cloud-outbox.store.d.ts.map +1 -0
  102. package/dist/cli/backend/src/services/chat-v2/sqlite/cloud-outbox.store.js +207 -0
  103. package/dist/cli/backend/src/services/chat-v2/sqlite/cloud-outbox.store.js.map +1 -0
  104. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts +62 -1
  105. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -1
  106. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js +111 -4
  107. package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -1
  108. package/dist/cli/backend/src/services/chat-v2/sqlite/unified-log.d.ts +206 -0
  109. package/dist/cli/backend/src/services/chat-v2/sqlite/unified-log.d.ts.map +1 -0
  110. package/dist/cli/backend/src/services/chat-v2/sqlite/unified-log.js +442 -0
  111. package/dist/cli/backend/src/services/chat-v2/sqlite/unified-log.js.map +1 -0
  112. package/dist/cli/backend/src/services/chat-v2/types.d.ts +29 -0
  113. package/dist/cli/backend/src/services/chat-v2/types.d.ts.map +1 -1
  114. package/dist/cli/backend/src/services/chat-v2/types.js.map +1 -1
  115. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.d.ts +1 -1
  116. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.d.ts.map +1 -1
  117. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js +13 -0
  118. package/dist/cli/backend/src/services/slack/slack-agent-dm.service.js.map +1 -1
  119. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +14 -1
  120. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
  121. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +34 -2
  122. package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
  123. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  124. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +1 -0
  125. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  126. package/dist/cli/backend/src/types/slack.types.d.ts +12 -0
  127. package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
  128. package/dist/cli/backend/src/types/slack.types.js.map +1 -1
  129. package/package.json +1 -1
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Unified conversation log — the columns, outbox and migration that make
3
+ * `chat.db` the machine's superset of every conversation surface
4
+ * (specs/unified-conversations-cloud-store.md §A.4).
5
+ *
6
+ * Every message row carries first-class copies of what used to be hidden in
7
+ * `metadata` JSON:
8
+ *
9
+ * - `source` — which surface it belongs to (slack, crewly-chat, cloud-talk, …)
10
+ * - `direction` — `in` (to an agent), `out` (from an agent), `internal`
11
+ * - `sender_kind` — `owner`, `agent`, `human` (someone else), `system`
12
+ * - `agent_session` — the agent the message is to/from (denormalised)
13
+ * - `ext_ref` — JSON of external ids (Slack workspace/channel/ts/thread, …)
14
+ * - `cloud_sync` — 1 when the row may leave the machine, 0 when not (owner
15
+ * decision O3: other people's chatter in shared Slack
16
+ * channels stays local)
17
+ *
18
+ * A trigger on `chat_messages` fills `cloud_outbox` for every syncable insert,
19
+ * so any writer — present or future, through `recordTurn` or not — produces
20
+ * an upload row.
21
+ *
22
+ * @module services/chat-v2/sqlite/unified-log
23
+ */
24
+ import { CONVERSATION_LOG_CONSTANTS } from '../../../constants.js';
25
+ import type { ChatChannelType, ChatSenderType } from '../types.js';
26
+ import type { ChatDatabase } from './chat-db.js';
27
+ /** Which surface a message belongs to. */
28
+ export type ConversationSource = (typeof CONVERSATION_LOG_CONSTANTS.SOURCES)[number];
29
+ /** Whether a message went to an agent, came from one, or neither. */
30
+ export type ConversationDirection = (typeof CONVERSATION_LOG_CONSTANTS.DIRECTIONS)[number];
31
+ /** Who wrote a message. */
32
+ export type ConversationSenderKind = (typeof CONVERSATION_LOG_CONSTANTS.SENDER_KINDS)[number];
33
+ /** External ids kept per message (only the fields that apply are present). */
34
+ export interface ConversationExtRef {
35
+ slackTeamId?: string;
36
+ slackChannelId?: string;
37
+ /** Slack message ts */
38
+ ts?: string;
39
+ /** Slack thread root ts */
40
+ threadTs?: string;
41
+ slackUserId?: string;
42
+ telegramChatId?: string;
43
+ telegramMessageId?: string;
44
+ gchatSpace?: string;
45
+ gchatThread?: string;
46
+ gchatMessage?: string;
47
+ whatsappChatId?: string;
48
+ }
49
+ /** The derived columns written with every insert. */
50
+ export interface UnifiedColumns {
51
+ source: ConversationSource;
52
+ direction: ConversationDirection;
53
+ senderKind: ConversationSenderKind;
54
+ agentSession: string | null;
55
+ extRef: ConversationExtRef | null;
56
+ /** 1 = may be uploaded to Cloud, 0 = stays on this machine. */
57
+ cloudSync: 0 | 1;
58
+ }
59
+ /** Who the owner is on the surfaces that carry other people too. */
60
+ export interface OwnerIdentity {
61
+ /** The owner's Slack user id (the person who installed the app), when known. */
62
+ slackUserId?: string | null;
63
+ /** The Slack workspace this machine serves, used when a row carries no team id. */
64
+ slackTeamId?: string | null;
65
+ }
66
+ /** Inputs of {@link deriveUnifiedColumns}. */
67
+ export interface DeriveUnifiedColumnsInput {
68
+ senderType: ChatSenderType;
69
+ senderId: string;
70
+ /** Parsed metadata (may be empty). */
71
+ metadata: Record<string, unknown>;
72
+ /** Mentioned agent sessions / member ids. */
73
+ mentions: readonly string[];
74
+ channel: {
75
+ id: string;
76
+ type: ChatChannelType;
77
+ agentSession: string;
78
+ };
79
+ /**
80
+ * `source` column of the channel's most recent `user` row. An agent's reply
81
+ * belongs to the surface it answers (spec §A.4: reply-tool takes the
82
+ * inbound channel's source).
83
+ */
84
+ latestInboundSource?: string | null;
85
+ owner?: OwnerIdentity | null;
86
+ }
87
+ /**
88
+ * Whether a value is one of the {@link ConversationSource} values.
89
+ *
90
+ * @param value - Candidate
91
+ * @returns True for a known source
92
+ */
93
+ export declare function isConversationSource(value: unknown): value is ConversationSource;
94
+ /**
95
+ * The surface a channel id names, for legacy rows whose tag is missing or
96
+ * wrong (WhatsApp rows were tagged `slack` before G2).
97
+ *
98
+ * @param channelId - chat-v2 channel id
99
+ * @returns The surface, or null when the id says nothing
100
+ */
101
+ export declare function sourceFromChannelId(channelId: string): ConversationSource | null;
102
+ /**
103
+ * Derive the unified-log columns for one message.
104
+ *
105
+ * Pure: every input is passed in, so the same function serves live inserts
106
+ * and the legacy-row backfill.
107
+ *
108
+ * O3 (owner decision 2026-09-28): in a shared Slack channel (a `huddle` or
109
+ * `channel` room), only messages involving this account's agents may leave
110
+ * the machine — the owner's own messages, any local agent's message, and a
111
+ * message that @-mentions one of our agents. Anyone else's chatter is
112
+ * `cloud_sync = 0`.
113
+ *
114
+ * @param input - Row and channel facts
115
+ * @returns The derived columns
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * deriveUnifiedColumns({
120
+ * senderType: 'user', senderId: 'Maya', mentions: [],
121
+ * metadata: { source: 'slack', slackUserId: 'U2' },
122
+ * channel: { id: 'huddle-1', type: 'huddle', agentSession: '' },
123
+ * owner: { slackUserId: 'U1' },
124
+ * }).cloudSync; // 0 — someone else's chatter
125
+ * ```
126
+ */
127
+ export declare function deriveUnifiedColumns(input: DeriveUnifiedColumnsInput): UnifiedColumns;
128
+ /**
129
+ * Parse a stored metadata / mentions JSON column without throwing.
130
+ *
131
+ * @param raw - Column value
132
+ * @returns Parsed object (metadata) — `{}` when missing or malformed
133
+ */
134
+ export declare function parseMetadataColumn(raw: string | null | undefined): Record<string, unknown>;
135
+ /**
136
+ * Parse a stored mentions JSON column without throwing.
137
+ *
138
+ * @param raw - Column value
139
+ * @returns String entries, `[]` when missing or malformed
140
+ */
141
+ export declare function parseMentionsColumn(raw: string | null | undefined): string[];
142
+ /** Columns added to `chat_messages` (name → ADD COLUMN clause). */
143
+ export declare const UNIFIED_LOG_MESSAGE_COLUMNS: ReadonlyArray<{
144
+ name: string;
145
+ addClause: string;
146
+ }>;
147
+ /**
148
+ * Outbox, sync state, per-agent index and outbox triggers. Idempotent.
149
+ *
150
+ * The triggers skip rows with `cloud_sync = 0` (O3). An update enqueues only
151
+ * when user-visible content changes or a row becomes syncable (owner
152
+ * reclassification); metadata-only bookkeeping does not.
153
+ */
154
+ export declare const UNIFIED_LOG_SQL = "\nCREATE INDEX IF NOT EXISTS ix_messages_agent_created\n ON chat_messages(agent_session, created_at DESC);\n\n-- Backfill pages history in created_at order (spec \u00A7B.4).\nCREATE INDEX IF NOT EXISTS ix_messages_created\n ON chat_messages(created_at);\n\nCREATE TABLE IF NOT EXISTS cloud_outbox (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n message_id TEXT NOT NULL,\n op TEXT NOT NULL CHECK(op IN ('upsert','delete')),\n enqueued_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS cloud_sync_state (\n k TEXT PRIMARY KEY,\n v TEXT\n);\n\nCREATE TRIGGER IF NOT EXISTS trg_outbox_ins AFTER INSERT ON chat_messages\n WHEN COALESCE(NEW.cloud_sync, 1) = 1\n BEGIN\n INSERT INTO cloud_outbox(message_id, op, enqueued_at) VALUES (NEW.id, 'upsert', NEW.created_at);\n END;\n\nCREATE TRIGGER IF NOT EXISTS trg_outbox_upd AFTER UPDATE OF content, cloud_sync ON chat_messages\n WHEN COALESCE(NEW.cloud_sync, 1) = 1\n AND (NEW.content IS NOT OLD.content OR COALESCE(OLD.cloud_sync, 1) <> 1)\n BEGIN\n INSERT INTO cloud_outbox(message_id, op, enqueued_at)\n VALUES (NEW.id, 'upsert', CAST(strftime('%s','now') AS INTEGER) * 1000);\n END;\n\nCREATE TRIGGER IF NOT EXISTS trg_outbox_del AFTER DELETE ON chat_messages\n WHEN COALESCE(OLD.cloud_sync, 1) = 1\n BEGIN\n INSERT INTO cloud_outbox(message_id, op, enqueued_at)\n VALUES (OLD.id, 'delete', CAST(strftime('%s','now') AS INTEGER) * 1000);\n END;\n";
155
+ /** Report of {@link applyUnifiedLogUpgrades}. */
156
+ export interface UnifiedLogUpgradeReport {
157
+ /** Columns added to `chat_messages` on this run. */
158
+ columnsAdded: string[];
159
+ /** Legacy rows given unified-log columns on this run. */
160
+ rowsBackfilled: number;
161
+ }
162
+ /**
163
+ * Bring `chat.db` up to the unified-log schema: add the columns, create the
164
+ * outbox / state tables, index and triggers, then backfill rows that have no
165
+ * `source` yet (every row written before this migration).
166
+ *
167
+ * Backfilled rows are NOT put in the outbox — the first-sign-in backfill
168
+ * (spec §B.4) uploads history within the plan's window.
169
+ *
170
+ * Safe on every boot: each step is a no-op once applied.
171
+ *
172
+ * @param db - The chat database handle
173
+ * @returns What changed
174
+ */
175
+ export declare function applyUnifiedLogUpgrades(db: ChatDatabase): UnifiedLogUpgradeReport;
176
+ /**
177
+ * Give legacy rows (`source IS NULL`) their unified-log columns, best effort.
178
+ *
179
+ * Runs in pages of {@link CONVERSATION_LOG_CONSTANTS.BACKFILL_BATCH_SIZE}
180
+ * rows ordered by channel and seq, carrying each channel's latest inbound
181
+ * source forward so agent replies inherit the surface they answered. The
182
+ * owner's Slack id is unknown at boot, so shared-channel Slack rows land as
183
+ * `human`; {@link reclassifyOwnerSlackRows} upgrades them once it is known.
184
+ *
185
+ * Nothing is enqueued: the update trigger fires only when `content` changes
186
+ * or `cloud_sync` rises from a non-1 value, and legacy rows have
187
+ * `cloud_sync IS NULL`, which the trigger already treats as 1.
188
+ *
189
+ * @param db - The chat database handle
190
+ * @returns Rows updated
191
+ */
192
+ export declare function backfillUnifiedColumns(db: ChatDatabase): number;
193
+ /**
194
+ * Once the owner's Slack user id is known, mark their Slack rows as the
195
+ * owner's and — in shared channels — syncable. Rows recorded before the id
196
+ * was available (legacy backfill, early boot) were conservatively `human`.
197
+ *
198
+ * Idempotent; the `cloud_sync` 0 → 1 flip enqueues those rows for upload
199
+ * through the update trigger.
200
+ *
201
+ * @param db - The chat database handle
202
+ * @param ownerSlackUserId - The owner's Slack user id
203
+ * @returns Rows reclassified
204
+ */
205
+ export declare function reclassifyOwnerSlackRows(db: ChatDatabase, ownerSlackUserId: string): number;
206
+ //# sourceMappingURL=unified-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unified-log.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/chat-v2/sqlite/unified-log.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,442 @@
1
+ /**
2
+ * Unified conversation log — the columns, outbox and migration that make
3
+ * `chat.db` the machine's superset of every conversation surface
4
+ * (specs/unified-conversations-cloud-store.md §A.4).
5
+ *
6
+ * Every message row carries first-class copies of what used to be hidden in
7
+ * `metadata` JSON:
8
+ *
9
+ * - `source` — which surface it belongs to (slack, crewly-chat, cloud-talk, …)
10
+ * - `direction` — `in` (to an agent), `out` (from an agent), `internal`
11
+ * - `sender_kind` — `owner`, `agent`, `human` (someone else), `system`
12
+ * - `agent_session` — the agent the message is to/from (denormalised)
13
+ * - `ext_ref` — JSON of external ids (Slack workspace/channel/ts/thread, …)
14
+ * - `cloud_sync` — 1 when the row may leave the machine, 0 when not (owner
15
+ * decision O3: other people's chatter in shared Slack
16
+ * channels stays local)
17
+ *
18
+ * A trigger on `chat_messages` fills `cloud_outbox` for every syncable insert,
19
+ * so any writer — present or future, through `recordTurn` or not — produces
20
+ * an upload row.
21
+ *
22
+ * @module services/chat-v2/sqlite/unified-log
23
+ */
24
+ import { CONVERSATION_LOG_CONSTANTS, OWNER_EVIDENCE_METADATA } from '../../../constants.js';
25
+ /** `metadata.source` values that tag an agent's reply rather than a surface. */
26
+ const REPLY_METADATA_SOURCES = new Set(['reply-tool', 'pty-runtime', 'in-process-runtime']);
27
+ /** Runtime capture tags — shown as `runtime` when nothing better is known. */
28
+ const RUNTIME_METADATA_SOURCES = new Set(['pty-runtime', 'in-process-runtime']);
29
+ /** Metadata sources that are already surface names. */
30
+ const DIRECT_METADATA_SOURCES = {
31
+ web: 'crewly-chat',
32
+ 'crewly-chat': 'crewly-chat',
33
+ slack: 'slack',
34
+ telegram: 'telegram',
35
+ 'google-chat': 'google-chat',
36
+ whatsapp: 'whatsapp',
37
+ 'cloud-talk': 'cloud-talk',
38
+ system: 'system',
39
+ };
40
+ /**
41
+ * Whether a value is one of the {@link ConversationSource} values.
42
+ *
43
+ * @param value - Candidate
44
+ * @returns True for a known source
45
+ */
46
+ export function isConversationSource(value) {
47
+ return typeof value === 'string' && CONVERSATION_LOG_CONSTANTS.SOURCES.includes(value);
48
+ }
49
+ /**
50
+ * The surface a channel id names, for legacy rows whose tag is missing or
51
+ * wrong (WhatsApp rows were tagged `slack` before G2).
52
+ *
53
+ * @param channelId - chat-v2 channel id
54
+ * @returns The surface, or null when the id says nothing
55
+ */
56
+ export function sourceFromChannelId(channelId) {
57
+ const id = channelId.toLowerCase();
58
+ const p = CONVERSATION_LOG_CONSTANTS.CHANNEL_PREFIXES;
59
+ if (id.startsWith(p.WHATSAPP))
60
+ return 'whatsapp';
61
+ if (id.startsWith(p.TELEGRAM))
62
+ return 'telegram';
63
+ if (id.startsWith(p.GOOGLE_CHAT))
64
+ return 'google-chat';
65
+ if (id.startsWith(p.SLACK))
66
+ return 'slack';
67
+ return null;
68
+ }
69
+ /**
70
+ * Read a non-empty string field.
71
+ *
72
+ * @param obj - Source object
73
+ * @param key - Field
74
+ * @returns The trimmed string, or undefined
75
+ */
76
+ function str(obj, key) {
77
+ const v = obj[key];
78
+ if (typeof v === 'string' && v.trim().length > 0)
79
+ return v;
80
+ if (typeof v === 'number' && Number.isFinite(v))
81
+ return String(v);
82
+ return undefined;
83
+ }
84
+ /**
85
+ * Resolve the `source` column.
86
+ *
87
+ * @param input - Derivation input
88
+ * @returns The surface
89
+ */
90
+ function deriveSource(input) {
91
+ const meta = str(input.metadata, 'source');
92
+ const byChannel = sourceFromChannelId(input.channel.id);
93
+ // Messenger channels are named after their surface; trust the name over a
94
+ // tag (a WhatsApp row tagged `slack` is WhatsApp).
95
+ if (byChannel && byChannel !== 'slack' && meta !== 'system')
96
+ return byChannel;
97
+ if (meta && DIRECT_METADATA_SOURCES[meta])
98
+ return DIRECT_METADATA_SOURCES[meta];
99
+ if (input.senderType === 'system')
100
+ return 'system';
101
+ if (input.senderType === 'agent' || (meta && REPLY_METADATA_SOURCES.has(meta))) {
102
+ if (isConversationSource(input.latestInboundSource))
103
+ return input.latestInboundSource;
104
+ if (byChannel)
105
+ return byChannel;
106
+ return meta && RUNTIME_METADATA_SOURCES.has(meta) ? 'runtime' : 'crewly-chat';
107
+ }
108
+ return byChannel ?? 'crewly-chat';
109
+ }
110
+ /**
111
+ * Resolve the `sender_kind` column.
112
+ *
113
+ * `owner` for the owner's own surfaces (Crewly Chat, Cloud Talk, messenger
114
+ * bridges — the same rows the approval gate reads as the owner). On Slack,
115
+ * where other people post too, `owner` only when the Slack user is the
116
+ * owner; when the owner's id is unknown a DM still counts as the owner and a
117
+ * shared channel does not.
118
+ *
119
+ * @param input - Derivation input
120
+ * @param source - Resolved source
121
+ * @returns The sender kind
122
+ */
123
+ function deriveSenderKind(input, source) {
124
+ if (input.senderType === 'agent')
125
+ return 'agent';
126
+ if (input.senderType === 'system')
127
+ return 'system';
128
+ const md = input.metadata;
129
+ if (str(md, OWNER_EVIDENCE_METADATA.AUTHOR_AGENT_SESSION) || str(md, OWNER_EVIDENCE_METADATA.REMOTE_AGENT_SESSION)) {
130
+ return 'agent';
131
+ }
132
+ const explicit = str(md, 'senderKind');
133
+ if (explicit && CONVERSATION_LOG_CONSTANTS.SENDER_KINDS.includes(explicit)) {
134
+ return explicit;
135
+ }
136
+ if (source !== 'slack')
137
+ return 'owner';
138
+ const slackUserId = str(md, 'slackUserId');
139
+ const ownerId = input.owner?.slackUserId ?? null;
140
+ if (slackUserId && ownerId)
141
+ return slackUserId === ownerId ? 'owner' : 'human';
142
+ return input.channel.type === 'dm' ? 'owner' : 'human';
143
+ }
144
+ /**
145
+ * Pick the external ids that apply to this row.
146
+ *
147
+ * @param md - Metadata
148
+ * @param source - Resolved source
149
+ * @param owner - Owner identity (supplies the Slack workspace when the row has none)
150
+ * @returns The ext ref, or null when there is nothing to keep
151
+ */
152
+ function deriveExtRef(md, source, owner) {
153
+ const ext = {};
154
+ const slackChannelId = str(md, 'slackChannelId');
155
+ if (slackChannelId || source === 'slack') {
156
+ const team = str(md, 'slackTeamId') ?? (slackChannelId ? owner?.slackTeamId ?? undefined : undefined);
157
+ if (team)
158
+ ext.slackTeamId = team;
159
+ if (slackChannelId)
160
+ ext.slackChannelId = slackChannelId;
161
+ const ts = str(md, 'slackTs');
162
+ if (ts)
163
+ ext.ts = ts;
164
+ const threadTs = str(md, 'slackThreadTs');
165
+ if (threadTs)
166
+ ext.threadTs = threadTs;
167
+ const user = str(md, 'slackUserId');
168
+ if (user)
169
+ ext.slackUserId = user;
170
+ }
171
+ const tgChat = str(md, 'telegramChatId');
172
+ if (tgChat)
173
+ ext.telegramChatId = tgChat;
174
+ const tgMsg = str(md, 'telegramMessageId');
175
+ if (tgMsg)
176
+ ext.telegramMessageId = tgMsg;
177
+ for (const key of ['gchatSpace', 'gchatThread', 'gchatMessage']) {
178
+ const v = str(md, key);
179
+ if (v)
180
+ ext[key] = v;
181
+ }
182
+ if (source === 'whatsapp') {
183
+ const chat = str(md, 'whatsappChatId') ?? str(md, 'chatId');
184
+ if (chat)
185
+ ext.whatsappChatId = chat;
186
+ }
187
+ return Object.keys(ext).length > 0 ? ext : null;
188
+ }
189
+ /**
190
+ * Derive the unified-log columns for one message.
191
+ *
192
+ * Pure: every input is passed in, so the same function serves live inserts
193
+ * and the legacy-row backfill.
194
+ *
195
+ * O3 (owner decision 2026-09-28): in a shared Slack channel (a `huddle` or
196
+ * `channel` room), only messages involving this account's agents may leave
197
+ * the machine — the owner's own messages, any local agent's message, and a
198
+ * message that @-mentions one of our agents. Anyone else's chatter is
199
+ * `cloud_sync = 0`.
200
+ *
201
+ * @param input - Row and channel facts
202
+ * @returns The derived columns
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * deriveUnifiedColumns({
207
+ * senderType: 'user', senderId: 'Maya', mentions: [],
208
+ * metadata: { source: 'slack', slackUserId: 'U2' },
209
+ * channel: { id: 'huddle-1', type: 'huddle', agentSession: '' },
210
+ * owner: { slackUserId: 'U1' },
211
+ * }).cloudSync; // 0 — someone else's chatter
212
+ * ```
213
+ */
214
+ export function deriveUnifiedColumns(input) {
215
+ const source = deriveSource(input);
216
+ const senderKind = deriveSenderKind(input, source);
217
+ const direction = input.senderType === 'agent' ? 'out' : input.senderType === 'system' ? 'internal' : 'in';
218
+ let agentSession = input.channel.agentSession || null;
219
+ if (!agentSession) {
220
+ if (input.senderType === 'agent')
221
+ agentSession = input.senderId || null;
222
+ else
223
+ agentSession = input.mentions.find((m) => m.length > 0) ?? null;
224
+ }
225
+ const sharedRoom = input.channel.type === 'huddle' || input.channel.type === 'channel';
226
+ const othersChatter = sharedRoom && source === 'slack' && input.senderType === 'user' && senderKind !== 'owner' && input.mentions.length === 0;
227
+ return {
228
+ source,
229
+ // Other people's chatter is not addressed to an agent: `internal`, which
230
+ // is also how Cloud's O3 filter reads it (`in` = delivered to an agent).
231
+ direction: othersChatter ? 'internal' : direction,
232
+ senderKind,
233
+ agentSession,
234
+ extRef: deriveExtRef(input.metadata, source, input.owner),
235
+ cloudSync: othersChatter ? 0 : 1,
236
+ };
237
+ }
238
+ /**
239
+ * Parse a stored metadata / mentions JSON column without throwing.
240
+ *
241
+ * @param raw - Column value
242
+ * @returns Parsed object (metadata) — `{}` when missing or malformed
243
+ */
244
+ export function parseMetadataColumn(raw) {
245
+ if (!raw)
246
+ return {};
247
+ try {
248
+ const parsed = JSON.parse(raw);
249
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
250
+ }
251
+ catch {
252
+ return {};
253
+ }
254
+ }
255
+ /**
256
+ * Parse a stored mentions JSON column without throwing.
257
+ *
258
+ * @param raw - Column value
259
+ * @returns String entries, `[]` when missing or malformed
260
+ */
261
+ export function parseMentionsColumn(raw) {
262
+ if (!raw)
263
+ return [];
264
+ try {
265
+ const parsed = JSON.parse(raw);
266
+ return Array.isArray(parsed) ? parsed.filter((m) => typeof m === 'string') : [];
267
+ }
268
+ catch {
269
+ return [];
270
+ }
271
+ }
272
+ // ---------------------------------------------------------------------------
273
+ // Migration
274
+ // ---------------------------------------------------------------------------
275
+ /** Columns added to `chat_messages` (name → ADD COLUMN clause). */
276
+ export const UNIFIED_LOG_MESSAGE_COLUMNS = [
277
+ { name: 'source', addClause: 'source TEXT' },
278
+ { name: 'direction', addClause: 'direction TEXT' },
279
+ { name: 'sender_kind', addClause: 'sender_kind TEXT' },
280
+ { name: 'agent_session', addClause: 'agent_session TEXT' },
281
+ { name: 'ext_ref', addClause: 'ext_ref TEXT' },
282
+ { name: 'cloud_sync', addClause: 'cloud_sync INTEGER' },
283
+ ];
284
+ /**
285
+ * Outbox, sync state, per-agent index and outbox triggers. Idempotent.
286
+ *
287
+ * The triggers skip rows with `cloud_sync = 0` (O3). An update enqueues only
288
+ * when user-visible content changes or a row becomes syncable (owner
289
+ * reclassification); metadata-only bookkeeping does not.
290
+ */
291
+ export const UNIFIED_LOG_SQL = `
292
+ CREATE INDEX IF NOT EXISTS ix_messages_agent_created
293
+ ON chat_messages(agent_session, created_at DESC);
294
+
295
+ -- Backfill pages history in created_at order (spec §B.4).
296
+ CREATE INDEX IF NOT EXISTS ix_messages_created
297
+ ON chat_messages(created_at);
298
+
299
+ CREATE TABLE IF NOT EXISTS cloud_outbox (
300
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
301
+ message_id TEXT NOT NULL,
302
+ op TEXT NOT NULL CHECK(op IN ('upsert','delete')),
303
+ enqueued_at INTEGER NOT NULL
304
+ );
305
+
306
+ CREATE TABLE IF NOT EXISTS cloud_sync_state (
307
+ k TEXT PRIMARY KEY,
308
+ v TEXT
309
+ );
310
+
311
+ CREATE TRIGGER IF NOT EXISTS trg_outbox_ins AFTER INSERT ON chat_messages
312
+ WHEN COALESCE(NEW.cloud_sync, 1) = 1
313
+ BEGIN
314
+ INSERT INTO cloud_outbox(message_id, op, enqueued_at) VALUES (NEW.id, 'upsert', NEW.created_at);
315
+ END;
316
+
317
+ CREATE TRIGGER IF NOT EXISTS trg_outbox_upd AFTER UPDATE OF content, cloud_sync ON chat_messages
318
+ WHEN COALESCE(NEW.cloud_sync, 1) = 1
319
+ AND (NEW.content IS NOT OLD.content OR COALESCE(OLD.cloud_sync, 1) <> 1)
320
+ BEGIN
321
+ INSERT INTO cloud_outbox(message_id, op, enqueued_at)
322
+ VALUES (NEW.id, 'upsert', CAST(strftime('%s','now') AS INTEGER) * 1000);
323
+ END;
324
+
325
+ CREATE TRIGGER IF NOT EXISTS trg_outbox_del AFTER DELETE ON chat_messages
326
+ WHEN COALESCE(OLD.cloud_sync, 1) = 1
327
+ BEGIN
328
+ INSERT INTO cloud_outbox(message_id, op, enqueued_at)
329
+ VALUES (OLD.id, 'delete', CAST(strftime('%s','now') AS INTEGER) * 1000);
330
+ END;
331
+ `;
332
+ /**
333
+ * Bring `chat.db` up to the unified-log schema: add the columns, create the
334
+ * outbox / state tables, index and triggers, then backfill rows that have no
335
+ * `source` yet (every row written before this migration).
336
+ *
337
+ * Backfilled rows are NOT put in the outbox — the first-sign-in backfill
338
+ * (spec §B.4) uploads history within the plan's window.
339
+ *
340
+ * Safe on every boot: each step is a no-op once applied.
341
+ *
342
+ * @param db - The chat database handle
343
+ * @returns What changed
344
+ */
345
+ export function applyUnifiedLogUpgrades(db) {
346
+ const existing = new Set(db.pragma('table_info(chat_messages)').map((c) => c.name));
347
+ const columnsAdded = [];
348
+ for (const col of UNIFIED_LOG_MESSAGE_COLUMNS) {
349
+ if (!existing.has(col.name)) {
350
+ db.exec(`ALTER TABLE chat_messages ADD COLUMN ${col.addClause}`);
351
+ columnsAdded.push(col.name);
352
+ }
353
+ }
354
+ db.exec(UNIFIED_LOG_SQL);
355
+ const rowsBackfilled = backfillUnifiedColumns(db);
356
+ return { columnsAdded, rowsBackfilled };
357
+ }
358
+ /**
359
+ * Give legacy rows (`source IS NULL`) their unified-log columns, best effort.
360
+ *
361
+ * Runs in pages of {@link CONVERSATION_LOG_CONSTANTS.BACKFILL_BATCH_SIZE}
362
+ * rows ordered by channel and seq, carrying each channel's latest inbound
363
+ * source forward so agent replies inherit the surface they answered. The
364
+ * owner's Slack id is unknown at boot, so shared-channel Slack rows land as
365
+ * `human`; {@link reclassifyOwnerSlackRows} upgrades them once it is known.
366
+ *
367
+ * Nothing is enqueued: the update trigger fires only when `content` changes
368
+ * or `cloud_sync` rises from a non-1 value, and legacy rows have
369
+ * `cloud_sync IS NULL`, which the trigger already treats as 1.
370
+ *
371
+ * @param db - The chat database handle
372
+ * @returns Rows updated
373
+ */
374
+ export function backfillUnifiedColumns(db) {
375
+ const pending = db.prepare('SELECT 1 FROM chat_messages WHERE source IS NULL LIMIT 1').get();
376
+ if (!pending)
377
+ return 0;
378
+ const select = db.prepare(`SELECT m.rowid AS rowid, m.id, m.channel_id, m.sender_type, m.sender_id, m.metadata, m.mentions,
379
+ c.type AS ch_type, c.agent_session AS ch_agent
380
+ FROM chat_messages m LEFT JOIN chat_channels c ON c.id = m.channel_id
381
+ WHERE m.source IS NULL
382
+ ORDER BY m.channel_id, m.seq
383
+ LIMIT ?`);
384
+ const update = db.prepare(`UPDATE chat_messages
385
+ SET source = ?, direction = ?, sender_kind = ?, agent_session = ?, ext_ref = ?, cloud_sync = ?
386
+ WHERE rowid = ?`);
387
+ const lastInbound = new Map();
388
+ let total = 0;
389
+ const batch = CONVERSATION_LOG_CONSTANTS.BACKFILL_BATCH_SIZE;
390
+ for (;;) {
391
+ const rows = select.all(batch);
392
+ if (rows.length === 0)
393
+ break;
394
+ db.transaction(() => {
395
+ for (const r of rows) {
396
+ const cols = deriveUnifiedColumns({
397
+ senderType: r.sender_type,
398
+ senderId: r.sender_id,
399
+ metadata: parseMetadataColumn(r.metadata),
400
+ mentions: parseMentionsColumn(r.mentions),
401
+ channel: { id: r.channel_id, type: r.ch_type ?? 'dm', agentSession: r.ch_agent ?? '' },
402
+ latestInboundSource: lastInbound.get(r.channel_id) ?? null,
403
+ owner: null,
404
+ });
405
+ if (r.sender_type === 'user')
406
+ lastInbound.set(r.channel_id, cols.source);
407
+ update.run(cols.source, cols.direction, cols.senderKind, cols.agentSession, cols.extRef ? JSON.stringify(cols.extRef) : null, cols.cloudSync, r.rowid);
408
+ }
409
+ })();
410
+ total += rows.length;
411
+ if (rows.length < batch)
412
+ break;
413
+ }
414
+ return total;
415
+ }
416
+ /**
417
+ * Once the owner's Slack user id is known, mark their Slack rows as the
418
+ * owner's and — in shared channels — syncable. Rows recorded before the id
419
+ * was available (legacy backfill, early boot) were conservatively `human`.
420
+ *
421
+ * Idempotent; the `cloud_sync` 0 → 1 flip enqueues those rows for upload
422
+ * through the update trigger.
423
+ *
424
+ * @param db - The chat database handle
425
+ * @param ownerSlackUserId - The owner's Slack user id
426
+ * @returns Rows reclassified
427
+ */
428
+ export function reclassifyOwnerSlackRows(db, ownerSlackUserId) {
429
+ if (!ownerSlackUserId)
430
+ return 0;
431
+ const result = db
432
+ .prepare(`UPDATE chat_messages
433
+ SET sender_kind = 'owner', cloud_sync = 1, direction = 'in'
434
+ WHERE source = 'slack' AND sender_type = 'user' AND sender_kind = 'human'
435
+ AND json_valid(metadata)
436
+ AND json_extract(metadata, '$.slackUserId') = ?
437
+ AND json_extract(metadata, '$.${OWNER_EVIDENCE_METADATA.AUTHOR_AGENT_SESSION}') IS NULL
438
+ AND json_extract(metadata, '$.${OWNER_EVIDENCE_METADATA.REMOTE_AGENT_SESSION}') IS NULL`)
439
+ .run(ownerSlackUserId);
440
+ return result.changes;
441
+ }
442
+ //# sourceMappingURL=unified-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unified-log.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/chat-v2/sqlite/unified-log.ts"],"names":[],"mappings":"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"}