@sentry/junior 0.268.0 → 0.269.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -9
- package/dist/{adapter-04AcD2SO.js → adapter-2-CG25be.js} +3 -49
- package/dist/{admin-DIU1QyG3.js → admin-DSs8S5Uy.js} +1 -1
- package/dist/{agent-runner-CAYfFmU7.js → agent-runner-CrDrPUtY.js} +3208 -2826
- package/dist/api/schema.d.ts +2 -3
- package/dist/api/schema.js +7 -7
- package/dist/api.d.ts +3 -1
- package/dist/api.js +157 -298
- package/dist/{app-z6Uuyq3j.d.ts → app-BrMMzjYm.d.ts} +8 -2
- package/dist/app.d.ts +1 -1
- package/dist/app.js +121 -207
- package/dist/{automation-CmeFBSEh.js → automation-p87hg_AV.js} +74 -74
- package/dist/{briefs-DQbV2jNr.js → briefs-PtODdLNu.js} +4 -4
- package/dist/{catalog-runtime-BD9GSpc6.js → catalog-runtime-BsoSmTM0.js} +1 -1
- package/dist/cli/chat.js +16 -26
- package/dist/cli/check.js +3 -3
- package/dist/cli/main.js +5 -3
- package/dist/cli/plugins.js +4 -4
- package/dist/cli/snapshot-create.js +4 -4
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +3 -3
- package/dist/{client-C1yDYjoS.js → client-Cqf4TboL.js} +20 -15
- package/dist/{config-DrTeF80T.js → config-CPsE4T5L.js} +2 -2
- package/dist/{connections-D46f8vvi.js → connections-upFXLmhG.js} +10 -10
- package/dist/{conversation-DSo310Ps.js → conversation-CTpD_PGk.js} +7 -84
- package/dist/{conversation-privacy-CxlOCiUo.js → conversation-privacy-D6zJc8ff.js} +1 -1
- package/dist/{credential-sync-sm9fegLY.js → credential-sync-CS_6IC-f.js} +15 -14
- package/dist/{credentialed-lukuD-cc.js → credentialed-BU-OW7HP.js} +548 -71
- package/dist/{store-xMuuXRE0.d.ts → db-BzOCzVb1.d.ts} +4 -0
- package/dist/{executor-4vhvsMZJ.js → executor-CNS7ONt-.js} +6 -6
- package/dist/instrumentation.js +2 -2
- package/dist/{interruption-marker-CkncDT8d.js → interruption-marker-g6Xup3Fk.js} +1 -1
- package/dist/{job-queue-In9QKz-I.js → job-queue-DI-k-Bbk.js} +2 -2
- package/dist/{list-BkgnXEEB.js → list-CGoQdXTp.js} +193 -656
- package/dist/locks-D-0h111u.js +48 -0
- package/dist/{logging-DLSpkSSr.js → logging-n7knse-O.js} +1 -1
- package/dist/{history-C84mlAwO.js → media-85PDJxO_.js} +83 -1
- package/dist/{message-BObyg59s.js → message-BpaWd8TT.js} +4 -4
- package/dist/nitro.d.ts +1 -1
- package/dist/nitro.js +3 -3
- package/dist/{oauth-callback-server-B-WbSdoH.js → oauth-callback-server-CpGmEw_i.js} +2 -2
- package/dist/{prompt-BW9jFsqZ.js → prompt-BlQB0QQt.js} +1 -1
- package/dist/{registry-BgecKva0.js → registry-By36_xsm.js} +1 -1
- package/dist/reporting-schema-CZREhYc-.js +104 -0
- package/dist/{web-message-id-DlHNANtd.js → reporting-window-CVarfId9.js} +130 -7
- package/dist/{resolve-Cpa4Bu1O.js → resolve-CUIBcdPd.js} +3 -3
- package/dist/{result-D3TSmZZ8.js → result-BemsOMOO.js} +30 -11
- package/dist/{runner-DS5OKhEp.js → runner-BVok7PAA.js} +12 -11
- package/dist/{sandbox-egress-signals-CnDjY5rx.js → sandbox-egress-signals-BSUWQI95.js} +1 -1
- package/dist/{schema-0050HFnB.js → schema-ChnFo5f2.js} +36 -20
- package/dist/{schema-ZngqcOWM.d.ts → schema-jK4-1u_H.d.ts} +126 -12
- package/dist/{service-account-CKV2WTY7.js → service-account-DHvlLnZw.js} +4 -3
- package/dist/{service-account-DGjnDw9y.js → service-account-hMmESDcr.js} +3 -3
- package/dist/{session-stop-Dam1EI7B.js → session-stop-BQW_iFcd.js} +15 -15
- package/dist/{skills-CfAnfImD.js → skills-DmJJIuLF.js} +2 -2
- package/dist/{spawn-cmq9p4Ly.js → spawn-ChCm46i-.js} +2 -2
- package/dist/{store-C5PN02QO.js → store-1DVhoKOL.js} +3 -3
- package/dist/{store-zPEoJPBB.js → store-DCo2n5iJ.js} +3 -3
- package/dist/{task-queue-_mf28dSu.js → task-queue-HMC4QjFm.js} +3 -3
- package/dist/task-runner-cA6yjn_E.js +269 -0
- package/dist/{thread-state-BbbPh0Sn.js → thread-state-CHy8LS0C.js} +5 -5
- package/dist/{turn-failure-response-BKKmnMB5.js → turn-failure-response-Dr7erLWs.js} +9 -9
- package/dist/{turn-lifecycle-oP0kVeGz.js → turn-lifecycle-bf2fIYi_.js} +1 -1
- package/dist/{tasks-SB9z-VTU.js → turn-session-routing-BqO0qXLk.js} +928 -116
- package/dist/{validation--JsAGLwc.js → validation-DHR1ivfh.js} +1 -1
- package/dist/{version-jHwJp4hC.js → version-CXCHLgWm.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{viewer-CpXTGlep.js → viewer-DSbD6DF6.js} +201 -27
- package/dist/{work-3N-GHY9m.js → work-BE_qTYGi.js} +17 -36
- package/dist/{worker-CCdbcItd.js → worker-Cm9_sWXg.js} +15 -7
- package/migrations/0069_system_identity.sql +25 -0
- package/migrations/0070_review_item_foreign_keys.sql +5 -0
- package/migrations/AGENTS.md +17 -0
- package/migrations/meta/0069_snapshot.json +3979 -0
- package/migrations/meta/0070_snapshot.json +3997 -0
- package/migrations/meta/_journal.json +15 -1
- package/package.json +4 -4
- package/dist/db-BU4LQD8s.d.ts +0 -6
- package/dist/history-BgI33Kie.d.ts +0 -78
- package/dist/reporting-window-DVCD_10C.js +0 -227
- package/dist/task-runner-DMI2prDO.js +0 -1073
- package/migrations/README.md +0 -30
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
|
|
2
|
-
import { n as pluginCatalogRuntime } from "./catalog-runtime-
|
|
2
|
+
import { n as pluginCatalogRuntime } from "./catalog-runtime-BsoSmTM0.js";
|
|
3
3
|
import { isDeepStrictEqual } from "node:util";
|
|
4
4
|
// src/chat/plugins/validation.ts
|
|
5
5
|
var validation_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.269.0";
|
|
3
3
|
// src/version.ts
|
|
4
4
|
/** Installed Junior package version, or `unknown` when package metadata is unavailable. */
|
|
5
5
|
const JUNIOR_VERSION = typeof version === "string" && version.trim() ? version : "unknown";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as JUNIOR_VERSION } from "./version-
|
|
1
|
+
export { t as JUNIOR_VERSION } from "./version-CXCHLgWm.js";
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
|
|
2
|
-
import { i as getChatConfig } from "./config-
|
|
3
|
-
import { L as GOOGLE_IDENTITY_PROVIDER, z as normalizeIdentityEmail } from "./logging-
|
|
2
|
+
import { i as getChatConfig } from "./config-CPsE4T5L.js";
|
|
3
|
+
import { L as GOOGLE_IDENTITY_PROVIDER, h as logWarn, z as normalizeIdentityEmail } from "./logging-n7knse-O.js";
|
|
4
4
|
import { a as isSlackUserId, d as parseSlackChannelReferenceId, f as parseSlackTeamId, s as parseActorUserId } from "./actor-CP9K2Idu.js";
|
|
5
5
|
import { c as juniorIdentities, d as juniorDestinations, l as juniorUsers, o as conversationReadColumns, s as juniorConversations } from "./agent-invocations-C1RNOCs8.js";
|
|
6
|
-
import { _ as CONVERSATION_METRICS, h as juniorConversationBindings, m as juniorConversationParticipants, t as createJuniorSqlExecutor, v as juniorConversationMetrics, x as juniorConversationAnnotations, y as juniorConversationEvents } from "./executor-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
6
|
+
import { _ as CONVERSATION_METRICS, h as juniorConversationBindings, m as juniorConversationParticipants, t as createJuniorSqlExecutor, v as juniorConversationMetrics, x as juniorConversationAnnotations, y as juniorConversationEvents } from "./executor-CNS7ONt-.js";
|
|
7
|
+
import { d as newConversationEventSchema, f as encodeHistoryPayload, l as decodeStoredConversationEvent, n as detectImageMediaType, o as conversationEventDataSchema, u as historyReplacementSchema, v as usageSchema } from "./media-85PDJxO_.js";
|
|
8
|
+
import { a as withLock } from "./locks-D-0h111u.js";
|
|
9
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
10
|
+
import { and, asc, desc, eq, gt, gte, inArray, isNotNull, isNull, lt, lte, ne, or, sql } from "drizzle-orm";
|
|
10
11
|
import { destinationSchema, identitySchema, locationSchema, sourceSchema, userSchema } from "@sentry/junior-plugin-api";
|
|
11
12
|
// src/chat/destination.ts
|
|
12
13
|
/** Build Junior's canonical destination from Slack workspace and channel ids. */
|
|
@@ -381,7 +382,7 @@ async function upsertIdentityRecord(executor, identity, linkedUserId, nowMs) {
|
|
|
381
382
|
email: email ?? null,
|
|
382
383
|
emailNormalized: emailNormalized ?? null,
|
|
383
384
|
emailVerified: Boolean(identity.emailVerified && emailNormalized),
|
|
384
|
-
avatarUrl: null,
|
|
385
|
+
avatarUrl: identity.avatarUrl ?? null,
|
|
385
386
|
metadata: identity.metadata ?? null,
|
|
386
387
|
createdAt: dateFromMs$1(nowMs),
|
|
387
388
|
updatedAt: dateFromMs$1(nowMs)
|
|
@@ -399,7 +400,7 @@ async function upsertIdentityRecord(executor, identity, linkedUserId, nowMs) {
|
|
|
399
400
|
email: sql`case when ${juniorIdentities.emailVerified} then coalesce(${juniorIdentities.email}, excluded.email) when excluded.email_verified then excluded.email else coalesce(${juniorIdentities.email}, excluded.email) end`,
|
|
400
401
|
emailNormalized: sql`case when ${juniorIdentities.emailVerified} then coalesce(${juniorIdentities.emailNormalized}, excluded.email_normalized) when excluded.email_verified then excluded.email_normalized else coalesce(${juniorIdentities.emailNormalized}, excluded.email_normalized) end`,
|
|
401
402
|
emailVerified: sql`${juniorIdentities.emailVerified} OR excluded.email_verified`,
|
|
402
|
-
avatarUrl: sql
|
|
403
|
+
avatarUrl: identity.avatarUrl === void 0 ? sql`${juniorIdentities.avatarUrl}` : sql`excluded.avatar_url`,
|
|
403
404
|
metadata: sql`coalesce(${juniorIdentities.metadata}, excluded.metadata_json)`,
|
|
404
405
|
updatedAt: sql`excluded.updated_at`
|
|
405
406
|
},
|
|
@@ -475,7 +476,8 @@ async function recordGoogleSignIn(executor, account, nowMs = Date.now()) {
|
|
|
475
476
|
providerSubjectId: account.subject,
|
|
476
477
|
email: account.email,
|
|
477
478
|
emailVerified: true,
|
|
478
|
-
displayName: account.displayName
|
|
479
|
+
displayName: account.displayName,
|
|
480
|
+
avatarUrl: account.avatarUrl ?? null
|
|
479
481
|
}, nowMs);
|
|
480
482
|
await executor.db().update(juniorIdentities).set({ signedInAt: dateFromMs$1(nowMs) }).where(eq(juniorIdentities.id, stored.id));
|
|
481
483
|
return stored;
|
|
@@ -488,20 +490,22 @@ async function recordGoogleSignIn(executor, account, nowMs = Date.now()) {
|
|
|
488
490
|
* module, so they agree on one Identity per person. The key for each Actor
|
|
489
491
|
* stays private here, so it can change without changing callers.
|
|
490
492
|
*/
|
|
491
|
-
/** The one Identity for work that Junior does on its own. */
|
|
492
|
-
const SYSTEM_IDENTITY = {
|
|
493
|
-
kind: "system",
|
|
494
|
-
provider: "junior",
|
|
495
|
-
providerSubjectId: "system",
|
|
496
|
-
displayName: "System"
|
|
497
|
-
};
|
|
498
493
|
/**
|
|
499
|
-
*
|
|
494
|
+
* The fixed id of the one system Identity: kind `system`, provider `junior`,
|
|
495
|
+
* an empty tenant, and subject `system`. A migration creates the row, so
|
|
496
|
+
* callers use this id without a database read.
|
|
497
|
+
*/
|
|
498
|
+
const SYSTEM_IDENTITY_ID = "system";
|
|
499
|
+
/**
|
|
500
|
+
* Return whether an Actor uses the system Identity.
|
|
500
501
|
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
* because the local runner has no per-person user id.
|
|
502
|
+
* `SystemActor.name` is provenance, not part of the Identity. A LocalActor
|
|
503
|
+
* uses the system Identity because the local runner has no per-person user id.
|
|
504
504
|
*/
|
|
505
|
+
function usesSystemIdentity(actor) {
|
|
506
|
+
return actor.platform === "local" || actor.platform === "system";
|
|
507
|
+
}
|
|
508
|
+
/** Return the Identity key for one Slack or web Actor. */
|
|
505
509
|
function identityKey(actor) {
|
|
506
510
|
switch (actor.platform) {
|
|
507
511
|
case "slack": return {
|
|
@@ -530,20 +534,28 @@ function identityKey(actor) {
|
|
|
530
534
|
metadata: { platform: "web" }
|
|
531
535
|
};
|
|
532
536
|
}
|
|
533
|
-
case "local":
|
|
534
|
-
case "system": return SYSTEM_IDENTITY;
|
|
535
537
|
}
|
|
536
538
|
}
|
|
537
539
|
/**
|
|
538
|
-
*
|
|
540
|
+
* Return the Identity id for one Actor. Save a Slack or web Identity first.
|
|
539
541
|
* Return undefined only for a web Actor without a verified email.
|
|
540
542
|
*/
|
|
541
543
|
async function resolveActorIdentityId(executor, actor, nowMs = Date.now()) {
|
|
544
|
+
if (usesSystemIdentity(actor)) return SYSTEM_IDENTITY_ID;
|
|
542
545
|
const key = identityKey(actor);
|
|
543
546
|
return key ? (await upsertIdentity(executor, key, nowMs)).id : void 0;
|
|
544
547
|
}
|
|
548
|
+
/** Save the profile photo on the Identity of one Slack Actor. Null clears it. */
|
|
549
|
+
async function saveActorAvatar(executor, actor, avatarUrl, nowMs = Date.now()) {
|
|
550
|
+
const key = identityKey(actor);
|
|
551
|
+
if (key) await upsertIdentity(executor, {
|
|
552
|
+
...key,
|
|
553
|
+
avatarUrl
|
|
554
|
+
}, nowMs);
|
|
555
|
+
}
|
|
545
556
|
/** Find the stored Identity id for one Actor without writing. */
|
|
546
557
|
async function findActorIdentityId(db, actor) {
|
|
558
|
+
if (usesSystemIdentity(actor)) return SYSTEM_IDENTITY_ID;
|
|
547
559
|
const key = identityKey(actor);
|
|
548
560
|
if (!key) return;
|
|
549
561
|
return (await db.select({ id: juniorIdentities.id }).from(juniorIdentities).where(and(eq(juniorIdentities.provider, key.provider), eq(juniorIdentities.providerTenantId, key.providerTenantId ?? ""), eq(juniorIdentities.providerSubjectId, key.providerSubjectId))).limit(1))[0]?.id;
|
|
@@ -1068,7 +1080,7 @@ var SqlStore = class {
|
|
|
1068
1080
|
sessionSource: sql`coalesce(${juniorConversations.sessionSource}, excluded.source_json)`,
|
|
1069
1081
|
location: sql`coalesce(excluded.location_json, ${juniorConversations.location})`,
|
|
1070
1082
|
destinationId: sql`coalesce(excluded.destination_id, ${juniorConversations.destinationId})`,
|
|
1071
|
-
actorIdentityId: sql`coalesce(
|
|
1083
|
+
actorIdentityId: sql`coalesce(${juniorConversations.actorIdentityId}, excluded.actor_identity_id)`,
|
|
1072
1084
|
creatorIdentityId: sql`coalesce(excluded.creator_identity_id, ${juniorConversations.creatorIdentityId})`,
|
|
1073
1085
|
credentialSubjectIdentityId: sql`coalesce(excluded.credential_subject_identity_id, ${juniorConversations.credentialSubjectIdentityId})`,
|
|
1074
1086
|
channelName: sql`coalesce(excluded.channel_name, ${juniorConversations.channelName})`,
|
|
@@ -1223,14 +1235,15 @@ async function findJuniorIdentityIdByDashboardAuthorId(executor, dashboardAuthor
|
|
|
1223
1235
|
}
|
|
1224
1236
|
/**
|
|
1225
1237
|
* Resolve the durable actor identity for one event payload.
|
|
1226
|
-
*
|
|
1227
|
-
*
|
|
1238
|
+
* Ingress sets `authorIdentityId` on new user Messages. Legacy writes without
|
|
1239
|
+
* it still guess the author from its data, and a new Message logs the guess.
|
|
1228
1240
|
*/
|
|
1229
1241
|
async function resolveEventActorIdentityId(executor, args) {
|
|
1230
1242
|
const data = args.data;
|
|
1231
1243
|
if (data.type !== "message" && data.type !== "message_updated") return;
|
|
1232
1244
|
if (typeof data.authorIdentityId === "string" && data.authorIdentityId) return data.authorIdentityId;
|
|
1233
1245
|
if (data.role !== "user") return;
|
|
1246
|
+
if (data.type === "message") logWarn("conversation.message.author_identity.missing", { "app.message.id": data.messageId });
|
|
1234
1247
|
const author = readAuthor(data.meta);
|
|
1235
1248
|
if (author.isBot === true) return;
|
|
1236
1249
|
const userId = parseActorUserId(author.userId);
|
|
@@ -1653,6 +1666,163 @@ async function closeDb() {
|
|
|
1653
1666
|
current = void 0;
|
|
1654
1667
|
await previous?.db.close();
|
|
1655
1668
|
}
|
|
1669
|
+
// src/chat/state/cache.ts
|
|
1670
|
+
const MAX_CACHE_KEY_LENGTH = 512;
|
|
1671
|
+
const LOAD_WAIT_MS = 1e4;
|
|
1672
|
+
/**
|
|
1673
|
+
* Create a cache in the state adapter under `junior:cache:{namespace}:`.
|
|
1674
|
+
* Entries are wrapped, so `null` and `false` values are cache hits.
|
|
1675
|
+
*/
|
|
1676
|
+
function createCache(state, namespace) {
|
|
1677
|
+
return { async getOrLoad(key, load, options) {
|
|
1678
|
+
if (!key.trim()) throw new Error("Cache key is required");
|
|
1679
|
+
if (key.length > MAX_CACHE_KEY_LENGTH) throw new Error("Cache key exceeds the maximum length");
|
|
1680
|
+
const cacheKey = `junior:cache:${namespace}:${key}`;
|
|
1681
|
+
const cached = async () => options?.refresh ? void 0 : await state.get(cacheKey) ?? void 0;
|
|
1682
|
+
await state.connect();
|
|
1683
|
+
const hit = await cached();
|
|
1684
|
+
if (hit) return hit.value;
|
|
1685
|
+
const attempt = await withLock(state, `${cacheKey}:load`, async () => {
|
|
1686
|
+
const loadedMeanwhile = await cached();
|
|
1687
|
+
if (loadedMeanwhile) return loadedMeanwhile.value;
|
|
1688
|
+
const entry = await load();
|
|
1689
|
+
if (entry.ttlMs !== void 0) {
|
|
1690
|
+
if (!(entry.ttlMs > 0)) throw new Error("Cache ttlMs must be positive");
|
|
1691
|
+
await state.set(cacheKey, { value: entry.value }, entry.ttlMs);
|
|
1692
|
+
}
|
|
1693
|
+
return entry.value;
|
|
1694
|
+
}, {
|
|
1695
|
+
keepAlive: true,
|
|
1696
|
+
waitMs: LOAD_WAIT_MS
|
|
1697
|
+
});
|
|
1698
|
+
if (attempt.acquired) return attempt.value;
|
|
1699
|
+
return (await load()).value;
|
|
1700
|
+
} };
|
|
1701
|
+
}
|
|
1702
|
+
// src/chat/identities/avatar.ts
|
|
1703
|
+
/**
|
|
1704
|
+
* Profile photos of people, saved on their native Slack and Google
|
|
1705
|
+
* identities. Junior serves each photo from its own API, so browsers never
|
|
1706
|
+
* load a photo from Slack or Google.
|
|
1707
|
+
*/
|
|
1708
|
+
const AVATAR_HOSTS = {
|
|
1709
|
+
slack: ["avatars.slack-edge.com"],
|
|
1710
|
+
[GOOGLE_IDENTITY_PROVIDER]: ["googleusercontent.com"]
|
|
1711
|
+
};
|
|
1712
|
+
const AVATAR_PROVIDERS = Object.keys(AVATAR_HOSTS);
|
|
1713
|
+
const AVATAR_CACHE_TTL_MS = 864e5;
|
|
1714
|
+
const AVATAR_FAILURE_TTL_MS = 3e5;
|
|
1715
|
+
const AVATAR_FETCH_TIMEOUT_MS = 5e3;
|
|
1716
|
+
const MAX_AVATAR_BYTES = 1048576;
|
|
1717
|
+
/**
|
|
1718
|
+
* Return the URL when it is HTTPS on a photo host of the provider or a
|
|
1719
|
+
* subdomain. Junior fetches only these URLs.
|
|
1720
|
+
*/
|
|
1721
|
+
function parseAvatarUrl(value, provider) {
|
|
1722
|
+
const hosts = AVATAR_HOSTS[provider];
|
|
1723
|
+
if (!hosts || !value || !URL.canParse(value)) return void 0;
|
|
1724
|
+
const url = new URL(value);
|
|
1725
|
+
if (url.protocol !== "https:" || url.username || url.password) return;
|
|
1726
|
+
const host = url.hostname.toLowerCase();
|
|
1727
|
+
return hosts.some((allowed) => host === allowed || host.endsWith(`.${allowed}`)) ? url.href : void 0;
|
|
1728
|
+
}
|
|
1729
|
+
function avatarPath(identityId) {
|
|
1730
|
+
return `/api/people/avatars/${encodeURIComponent(identityId)}`;
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Read the photo path of each user: the Slack photo, then the Google photo.
|
|
1734
|
+
* Users without a photo are not in the map.
|
|
1735
|
+
*/
|
|
1736
|
+
async function readUserAvatarUrls(db, userIds) {
|
|
1737
|
+
if (userIds.length === 0) return /* @__PURE__ */ new Map();
|
|
1738
|
+
const rows = await db.select({
|
|
1739
|
+
identityId: juniorIdentities.id,
|
|
1740
|
+
provider: juniorIdentities.provider,
|
|
1741
|
+
updatedAt: juniorIdentities.updatedAt,
|
|
1742
|
+
userId: juniorIdentities.userId
|
|
1743
|
+
}).from(juniorIdentities).where(and(inArray(juniorIdentities.userId, [...new Set(userIds)]), eq(juniorIdentities.kind, "user"), inArray(juniorIdentities.provider, AVATAR_PROVIDERS), isNotNull(juniorIdentities.avatarUrl)));
|
|
1744
|
+
const rank = (row) => AVATAR_PROVIDERS.indexOf(row.provider);
|
|
1745
|
+
rows.sort((left, right) => rank(left) - rank(right) || right.updatedAt.getTime() - left.updatedAt.getTime());
|
|
1746
|
+
const avatars = /* @__PURE__ */ new Map();
|
|
1747
|
+
for (const row of rows) if (row.userId && !avatars.has(row.userId)) avatars.set(row.userId, avatarPath(row.identityId));
|
|
1748
|
+
return avatars;
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* Return the photo path for one Identity row. A linked Identity uses the
|
|
1752
|
+
* photo of its user. An Identity without a user uses its own photo.
|
|
1753
|
+
*/
|
|
1754
|
+
function identityAvatarUrl(row, userAvatarUrls) {
|
|
1755
|
+
if (row.userId) return userAvatarUrls.get(row.userId);
|
|
1756
|
+
return row.avatarUrl ? avatarPath(row.identityId) : void 0;
|
|
1757
|
+
}
|
|
1758
|
+
async function readBoundedBody(response, maxBytes) {
|
|
1759
|
+
if (!response.body) return /* @__PURE__ */ new Uint8Array();
|
|
1760
|
+
const reader = response.body.getReader();
|
|
1761
|
+
const chunks = [];
|
|
1762
|
+
let total = 0;
|
|
1763
|
+
while (true) {
|
|
1764
|
+
const { done, value } = await reader.read();
|
|
1765
|
+
if (done) return Buffer.concat(chunks);
|
|
1766
|
+
total += value.byteLength;
|
|
1767
|
+
if (total > maxBytes) {
|
|
1768
|
+
await reader.cancel();
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
chunks.push(value);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
function avatarFetchFailed(host, attributes) {
|
|
1775
|
+
logWarn("avatar.fetch.failed", {
|
|
1776
|
+
"server.address": host,
|
|
1777
|
+
...attributes
|
|
1778
|
+
});
|
|
1779
|
+
return null;
|
|
1780
|
+
}
|
|
1781
|
+
async function fetchAvatarImage(url) {
|
|
1782
|
+
const host = new URL(url).hostname;
|
|
1783
|
+
let data;
|
|
1784
|
+
try {
|
|
1785
|
+
const response = await fetch(url, {
|
|
1786
|
+
redirect: "error",
|
|
1787
|
+
signal: AbortSignal.timeout(AVATAR_FETCH_TIMEOUT_MS)
|
|
1788
|
+
});
|
|
1789
|
+
if (!response.ok) {
|
|
1790
|
+
await response.body?.cancel();
|
|
1791
|
+
return avatarFetchFailed(host, { "http.response.status_code": response.status });
|
|
1792
|
+
}
|
|
1793
|
+
data = await readBoundedBody(response, MAX_AVATAR_BYTES);
|
|
1794
|
+
} catch (error) {
|
|
1795
|
+
return avatarFetchFailed(host, { "exception.message": error instanceof Error ? error.message : String(error) });
|
|
1796
|
+
}
|
|
1797
|
+
if (!data) return avatarFetchFailed(host, { "app.reason": "too_large" });
|
|
1798
|
+
const contentType = detectImageMediaType(data);
|
|
1799
|
+
if (!contentType) return avatarFetchFailed(host, { "app.reason": "not_an_image" });
|
|
1800
|
+
return {
|
|
1801
|
+
contentType,
|
|
1802
|
+
data: Buffer.from(data).toString("base64")
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Read the saved photo of one Identity. Junior fetches it from the provider
|
|
1807
|
+
* once and keeps it in the state cache for a day. Return undefined when the
|
|
1808
|
+
* Identity has no photo or the provider does not return one.
|
|
1809
|
+
*/
|
|
1810
|
+
async function readAvatarImage(db, state, identityId) {
|
|
1811
|
+
const [row] = await db.select({
|
|
1812
|
+
avatarUrl: juniorIdentities.avatarUrl,
|
|
1813
|
+
provider: juniorIdentities.provider
|
|
1814
|
+
}).from(juniorIdentities).where(and(eq(juniorIdentities.id, identityId), eq(juniorIdentities.kind, "user"))).limit(1);
|
|
1815
|
+
const url = row ? parseAvatarUrl(row.avatarUrl, row.provider) : void 0;
|
|
1816
|
+
if (!url) return void 0;
|
|
1817
|
+
const key = `avatar:${createHash("sha256").update(url).digest("hex")}`;
|
|
1818
|
+
return await createCache(state, "core").getOrLoad(key, async () => {
|
|
1819
|
+
const loaded = await fetchAvatarImage(url);
|
|
1820
|
+
return {
|
|
1821
|
+
value: loaded,
|
|
1822
|
+
ttlMs: loaded ? AVATAR_CACHE_TTL_MS : AVATAR_FAILURE_TTL_MS
|
|
1823
|
+
};
|
|
1824
|
+
}) ?? void 0;
|
|
1825
|
+
}
|
|
1656
1826
|
// src/chat/plugins/viewer.ts
|
|
1657
1827
|
function identityFromRow(row) {
|
|
1658
1828
|
return identitySchema.parse({
|
|
@@ -1712,6 +1882,10 @@ async function updateViewerDisplayNameFromSql(db, userId, displayName) {
|
|
|
1712
1882
|
}).where(eq(juniorUsers.id, userId)).returning())[0];
|
|
1713
1883
|
return userRow ? await readUserById(db, userRow) : void 0;
|
|
1714
1884
|
}
|
|
1885
|
+
/** Read the photo path of one canonical user: Slack, then Google. */
|
|
1886
|
+
async function readViewerAvatarUrl(userId) {
|
|
1887
|
+
return (await readUserAvatarUrls(getDb(), [userId])).get(userId);
|
|
1888
|
+
}
|
|
1715
1889
|
/** Update one canonical user's display name. */
|
|
1716
1890
|
async function updateViewerDisplayName(userId, displayName) {
|
|
1717
1891
|
return await updateViewerDisplayNameFromSql(getDb(), userId, displayName);
|
|
@@ -1761,4 +1935,4 @@ async function readIdentityByIdFromSql(db, identityId) {
|
|
|
1761
1935
|
async function readIdentityById(identityId) {
|
|
1762
1936
|
return await readIdentityByIdFromSql(getDb(), identityId);
|
|
1763
1937
|
}
|
|
1764
|
-
export {
|
|
1938
|
+
export { startLocationThread as A, upsertLinkedIdentity as B, getSqlExecutor as C, createSqlStore as D, loadConversationSlackTenantIds as E, deleteProviderIdentityForUser as F, agentTurnTotalTokens as G, parseSessionSource as H, findUserByVerifiedEmails as I, resolveRootConversationId as J, agentTurnUsageSchema as K, readUserTimezone as L, resolveActorIdentityId as M, saveActorAvatar as N, withConversationMutationLock as O, deleteProviderIdentityForSlackUser as P, sameDestination as Q, recordGoogleSignIn as R, getDb as S, withConversationEventLock as T, addAgentTurnUsage as U, bindProviderConversation as V, agentTurnCostUsd as W, parseDestination as X, createSlackDestination as Y, requireSlackDestination as Z, closeDb as _, readActorIdentityFromSql as a, getConversationMessageSearchStore as b, readViewerAvatarUrl as c, updateViewerDisplayName as d, identityAvatarUrl as f, createCache as g, readUserAvatarUrls as h, readActorIdentity as i, SYSTEM_IDENTITY_ID as j, locationFromRow as k, resolveViewerUser as l, readAvatarImage as m, findUserByEmailFromSql as n, readIdentityById as o, parseAvatarUrl as p, hasAgentTurnUsage as q, listAdminUsersFromSql as r, readIdentityByIdFromSql as s, findUserByEmail as t, setUserAdminFromSql as u, db_exports as v, createSqlConversationEventStore as w, getConversationStore as x, getConversationEventStore as y, saveUserTimezone as z };
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { a as persistThreadStateById, n as getPersistedSandboxState, r as getPersistedThreadState } from "./thread-state-BbbPh0Sn.js";
|
|
1
|
+
import { Dn as failTurnRecord, Nt as openConversationProjection, On as getTurnRecord, Tn as abandonTurnRecord, dn as PluginCredentialFailureError, hn as AuthorizationFlowDisabledError, mr as isTurnExecutionLimitExceededError, vr as getTerminalAssistantMessages, wn as saveTurnCheckpoint } from "./turn-session-routing-BqO0qXLk.js";
|
|
2
|
+
import { n as botConfig } from "./config-CPsE4T5L.js";
|
|
3
|
+
import { j as SYSTEM_IDENTITY_ID, x as getConversationStore, y as getConversationEventStore } from "./viewer-DSbD6DF6.js";
|
|
4
|
+
import { _ as sameActorIdentity, p as agentInstructionProvenance } from "./media-85PDJxO_.js";
|
|
5
|
+
import { Et as withoutSavedSteeringMessages, Mt as readBlockedReport, Tt as buildSteeringPiMessage, _t as recordSubagentEnded, ht as stopChildAgentsOf, vt as recordSubagentStarted } from "./agent-runner-CrDrPUtY.js";
|
|
6
|
+
import { E as markAgentInvocationSteered, H as TurnInputCommitLostError, K as isTurnInputCommitLostError, L as agentTypeInstructions, O as reportReplacedAgentInvocation, R as agentTypeStartsWithParentHistory, S as markAgentInvocationMailboxAppended, T as markAgentInvocationRunning, W as getConversationTurnBoundaryError, _ as getLatestAgentInvocationForConversation, c as getAgentInvocation, f as getAgentInvocationMessageId, m as getAgentInvocationTurnId, n as completeAgentInvocation, r as createAgentInvocation, s as getActiveAgentInvocationForConversation, w as markAgentInvocationPaused, y as isTerminalAgentInvocation } from "./store-1DVhoKOL.js";
|
|
7
|
+
import { r as getAssistantReplyText } from "./interruption-marker-g6Xup3Fk.js";
|
|
8
|
+
import { C as maybePostAutomatedTurnLimitNotice, D as recordFinishedTurnForAutomatedLimit, O as resetAutomatedTurnLimit } from "./session-stop-BQW_iFcd.js";
|
|
9
|
+
import { r as appendAndEnqueueInboundMessage } from "./store-DCo2n5iJ.js";
|
|
10
|
+
import { t as ConversationTurnLifecycleService } from "./turn-lifecycle-bf2fIYi_.js";
|
|
11
|
+
import { a as persistThreadStateById, n as getPersistedSandboxState, r as getPersistedThreadState } from "./thread-state-CHy8LS0C.js";
|
|
13
12
|
import { isDeepStrictEqual } from "node:util";
|
|
14
13
|
import { z } from "zod";
|
|
15
14
|
// src/chat/runtime/turn-execution.ts
|
|
@@ -86,16 +85,6 @@ async function loadForkHistory(invocation) {
|
|
|
86
85
|
return forkHistoryFrom((await openConversationProjection({ conversationId: invocation.parentConversationId })).messages, invocation.parentToolCallId);
|
|
87
86
|
}
|
|
88
87
|
// src/chat/agent-invocations/work.ts
|
|
89
|
-
var work_exports = /* @__PURE__ */ __exportAll({
|
|
90
|
-
agentInvocationInputText: () => agentInvocationInputText,
|
|
91
|
-
agentInvocationRunPolicy: () => agentInvocationRunPolicy,
|
|
92
|
-
buildAgentInvocationInboundMessage: () => buildAgentInvocationInboundMessage,
|
|
93
|
-
createAgentInvocationWorker: () => createAgentInvocationWorker,
|
|
94
|
-
createAndEnqueueAgentInvocation: () => createAndEnqueueAgentInvocation,
|
|
95
|
-
enqueueAgentInvocation: () => enqueueAgentInvocation,
|
|
96
|
-
resolveAgentInvocationId: () => resolveAgentInvocationId,
|
|
97
|
-
routeAgentInvocationWork: () => routeAgentInvocationWork
|
|
98
|
-
});
|
|
99
88
|
const agentInvocationMailboxMetadataSchema = z.object({
|
|
100
89
|
agentInvocationId: z.string().min(1),
|
|
101
90
|
kind: z.literal("agent_invocation")
|
|
@@ -121,6 +110,7 @@ function buildAgentInvocationInboundMessage(invocation, nowMs = Date.now()) {
|
|
|
121
110
|
inboundMessageId: getAgentInvocationMessageId(invocation.invocationId),
|
|
122
111
|
input: {
|
|
123
112
|
authorId: invocation.invocationId,
|
|
113
|
+
authorIdentityId: SYSTEM_IDENTITY_ID,
|
|
124
114
|
text: "[agent invocation]",
|
|
125
115
|
metadata: {
|
|
126
116
|
agentInvocationId: invocation.invocationId,
|
|
@@ -188,8 +178,7 @@ async function resolveAgentInvocationId(context) {
|
|
|
188
178
|
if (context.attempt.messages.length > 0) return;
|
|
189
179
|
const active = await getActiveAgentInvocationForConversation(context.conversationId);
|
|
190
180
|
if (active) return active.invocationId;
|
|
191
|
-
|
|
192
|
-
return latest?.status === "stopped" ? latest.invocationId : void 0;
|
|
181
|
+
return (await getLatestAgentInvocationForConversation(context.conversationId))?.invocationId;
|
|
193
182
|
}
|
|
194
183
|
/**
|
|
195
184
|
* Give queued parent messages to the running agent invocation of a child as
|
|
@@ -267,9 +256,9 @@ function agentInvocationTurnEnd(terminal) {
|
|
|
267
256
|
/** Project the terminal invocation into the idempotent turn lifecycle. */
|
|
268
257
|
async function persistTerminalLifecycle(terminal) {
|
|
269
258
|
const turnId = getAgentInvocationTurnId(terminal.invocationId);
|
|
270
|
-
if (terminal.status
|
|
259
|
+
if (terminal.status !== "completed") await abandonTurnRecord({
|
|
271
260
|
conversationId: terminal.childConversationId,
|
|
272
|
-
errorMessage: "Turn cancelled",
|
|
261
|
+
errorMessage: terminal.status === "stopped" ? "Turn cancelled" : terminal.errorMessage,
|
|
273
262
|
turnId
|
|
274
263
|
});
|
|
275
264
|
await finishTurn(terminal.childConversationId, turnId, agentInvocationTurnEnd(terminal));
|
|
@@ -506,8 +495,7 @@ function createAgentInvocationWorker(agentRunner, options = {}) {
|
|
|
506
495
|
turnId
|
|
507
496
|
}));
|
|
508
497
|
} catch (error) {
|
|
509
|
-
|
|
510
|
-
const runError = error.cause;
|
|
498
|
+
const runError = error instanceof AgentRunError ? error.cause : error;
|
|
511
499
|
if (isInvocationInputCommitLost(runError)) return { status: "lost_lease" };
|
|
512
500
|
if (stopSignal?.aborted) return await finishStopped();
|
|
513
501
|
const blocking = blockingInvocationError(runError);
|
|
@@ -553,11 +541,4 @@ function createAgentInvocationWorker(agentRunner, options = {}) {
|
|
|
553
541
|
return { status: "completed" };
|
|
554
542
|
};
|
|
555
543
|
}
|
|
556
|
-
|
|
557
|
-
function routeAgentInvocationWork(options) {
|
|
558
|
-
return async (context) => {
|
|
559
|
-
const invocationId = await resolveAgentInvocationId(context);
|
|
560
|
-
return invocationId ? await options.invocationWorker(context, invocationId) : await options.fallbackWorker(context);
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
export { work_exports as a, resolveAgentInvocationId as i, createAndEnqueueAgentInvocation as n, AgentRunError as o, enqueueAgentInvocation as r, executeTurn as s, createAgentInvocationWorker as t };
|
|
544
|
+
export { AgentRunError as a, resolveAgentInvocationId as i, createAndEnqueueAgentInvocation as n, executeTurn as o, enqueueAgentInvocation as r, createAgentInvocationWorker as t };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-nwUcYXjP.js";
|
|
2
|
-
import {
|
|
3
|
-
import { y as isProviderRetryError } from "./client-
|
|
4
|
-
import { i as getChatConfig } from "./config-
|
|
5
|
-
import { T as withLogContext, b as setSpanAttributes, h as logWarn, m as logInfo, p as logException } from "./logging-
|
|
6
|
-
import { f as getTurnRequestDeadline, m as ConversationQueueMessageRejectedError } from "./job-queue-
|
|
7
|
-
import { C as requestAnotherSlice, T as startConversationWork, b as recordConversationRetry, d as countPendingConversationMessages, f as deadLetterAttempt, g as getConversationWorkState, i as beginConversationResume, l as completeConversationStop, m as ensureConversationWake, o as checkInConversationWork, p as drainConversationMailbox, s as clearConsumedConversationWake, t as ackMessages, u as completeConversationWork, x as releaseConversationWork, y as recordAttemptFailure } from "./store-
|
|
2
|
+
import { Xn as isInvalidConversationRecordError, Yn as isFinalAttempt, qn as hasConversationStop } from "./turn-session-routing-BqO0qXLk.js";
|
|
3
|
+
import { y as isProviderRetryError } from "./client-Cqf4TboL.js";
|
|
4
|
+
import { i as getChatConfig } from "./config-CPsE4T5L.js";
|
|
5
|
+
import { T as withLogContext, b as setSpanAttributes, h as logWarn, m as logInfo, p as logException } from "./logging-n7knse-O.js";
|
|
6
|
+
import { f as getTurnRequestDeadline, m as ConversationQueueMessageRejectedError } from "./job-queue-DI-k-Bbk.js";
|
|
7
|
+
import { C as requestAnotherSlice, T as startConversationWork, b as recordConversationRetry, d as countPendingConversationMessages, f as deadLetterAttempt, g as getConversationWorkState, i as beginConversationResume, l as completeConversationStop, m as ensureConversationWake, o as checkInConversationWork, p as drainConversationMailbox, s as clearConsumedConversationWake, t as ackMessages, u as completeConversationWork, x as releaseConversationWork, y as recordAttemptFailure } from "./store-DCo2n5iJ.js";
|
|
8
8
|
// src/chat/task-execution/attempt-selection.ts
|
|
9
9
|
function selectContiguousTurnBatch(messages) {
|
|
10
10
|
const first = messages[0];
|
|
@@ -116,6 +116,14 @@ async function recordFailedDeliveryAttempt(args) {
|
|
|
116
116
|
function isTerminalFailure(failure) {
|
|
117
117
|
return failure.status === "recorded" && failure.deadLetteredMessages.length > 0 && failure.pendingCount === 0;
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Return whether a failure of this attempt can end the conversation work. The
|
|
121
|
+
* retry limit applies to every attempt, also one with no messages, such as a
|
|
122
|
+
* resume. A message also ends it at its own delivery limit.
|
|
123
|
+
*/
|
|
124
|
+
function isLastConversationAttempt(work, messages) {
|
|
125
|
+
return (work.execution.retryCount ?? 0) + 1 >= 5 || messages.some((message) => isFinalAttempt(message));
|
|
126
|
+
}
|
|
119
127
|
function logRetryExhausted(args) {
|
|
120
128
|
logException(args.error, "conversation.work.retry.exhausted", {
|
|
121
129
|
"app.run.id": args.work.execution.runId ?? "unknown",
|
|
@@ -403,7 +411,7 @@ async function processConversationWorkInContext(message, options) {
|
|
|
403
411
|
conversationId,
|
|
404
412
|
destination,
|
|
405
413
|
drain,
|
|
406
|
-
isFinalAttempt:
|
|
414
|
+
isFinalAttempt: isLastConversationAttempt(leasedWork, attemptMessages),
|
|
407
415
|
messages: attemptMessages
|
|
408
416
|
},
|
|
409
417
|
conversationId,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-- Create the one system Identity with the fixed id `system`. Code writes this
|
|
2
|
+
-- id without a read, and Message and Conversation rows reference it through
|
|
3
|
+
-- foreign keys, so the row must exist. ON CONFLICT DO NOTHING makes a second
|
|
4
|
+
-- run do nothing.
|
|
5
|
+
INSERT INTO "junior_identities" (
|
|
6
|
+
"id",
|
|
7
|
+
"kind",
|
|
8
|
+
"provider",
|
|
9
|
+
"provider_tenant_id",
|
|
10
|
+
"provider_subject_id",
|
|
11
|
+
"display_name",
|
|
12
|
+
"created_at",
|
|
13
|
+
"updated_at"
|
|
14
|
+
)
|
|
15
|
+
VALUES (
|
|
16
|
+
'system',
|
|
17
|
+
'system',
|
|
18
|
+
'junior',
|
|
19
|
+
'',
|
|
20
|
+
'system',
|
|
21
|
+
'System',
|
|
22
|
+
CURRENT_TIMESTAMP,
|
|
23
|
+
CURRENT_TIMESTAMP
|
|
24
|
+
)
|
|
25
|
+
ON CONFLICT DO NOTHING;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
ALTER TABLE "junior_review_items" DROP CONSTRAINT "junior_review_items_closed_by_user_id_junior_users_id_fk";
|
|
2
|
+
--> statement-breakpoint
|
|
3
|
+
ALTER TABLE "junior_review_item_slack_cards" ADD CONSTRAINT "junior_review_item_slack_cards_conversation_id_junior_conversations_conversation_id_fk" FOREIGN KEY ("conversation_id") REFERENCES "public"."junior_conversations"("conversation_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
4
|
+
ALTER TABLE "junior_review_items" ADD CONSTRAINT "junior_review_items_added_by_conversation_id_junior_conversations_conversation_id_fk" FOREIGN KEY ("added_by_conversation_id") REFERENCES "public"."junior_conversations"("conversation_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
5
|
+
ALTER TABLE "junior_review_items" ADD CONSTRAINT "junior_review_items_closed_by_user_id_junior_users_id_fk" FOREIGN KEY ("closed_by_user_id") REFERENCES "public"."junior_users"("id") ON DELETE set null ON UPDATE no action;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# SQL Migrations
|
|
2
|
+
|
|
3
|
+
`src/db/schema.ts` is the Drizzle schema entry point. This directory is the
|
|
4
|
+
append-only history that brings an existing database to that schema.
|
|
5
|
+
`src/chat/conversations/sql/migrations.ts` owns loading, locking, and the
|
|
6
|
+
bridge-version guard.
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
- Never edit, rename, reorder, or delete an applied migration or its
|
|
11
|
+
metadata. Add a new migration to correct it.
|
|
12
|
+
- Change the table under `src/db/schema/`, then run
|
|
13
|
+
`pnpm --filter @sentry/junior db:generate --name <name>`. Commit the SQL,
|
|
14
|
+
`meta/_journal.json`, and the snapshot together.
|
|
15
|
+
- Put fixed row transformations in the migration that needs them.
|
|
16
|
+
- A schema without the core Drizzle journal must first upgrade through
|
|
17
|
+
`@sentry/junior@0.107.1`. Do not remove that guard.
|