@sentry/junior 0.162.0 → 0.164.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.
Files changed (96) hide show
  1. package/README.md +2 -0
  2. package/dist/{agent-hooks-TL2SGC5W.js → agent-hooks-WTBPNRTC.js} +10 -10
  3. package/dist/api/acp/route.d.ts +13 -0
  4. package/dist/api/schema/workspace.d.ts +47 -0
  5. package/dist/api/schema.d.ts +2 -0
  6. package/dist/api/schema.js +15 -3
  7. package/dist/api/workspaces/routes.d.ts +4 -0
  8. package/dist/api.js +321 -408
  9. package/dist/app.d.ts +2 -2
  10. package/dist/app.js +482 -38
  11. package/dist/{catalog-runtime-IGSMIADJ.js → catalog-runtime-CZ2SQINY.js} +5 -5
  12. package/dist/chat/agent/sandbox.d.ts +4 -0
  13. package/dist/chat/api-turns/cancellation.d.ts +27 -0
  14. package/dist/chat/api-turns/routing.d.ts +31 -0
  15. package/dist/chat/api-turns/work.d.ts +12 -29
  16. package/dist/chat/app/conversation-work.d.ts +6 -1
  17. package/dist/chat/app/production.d.ts +2 -2
  18. package/dist/chat/conversations/history.d.ts +3 -0
  19. package/dist/chat/conversations/turn-lifecycle.d.ts +1 -1
  20. package/dist/chat/experimental.d.ts +1 -1
  21. package/dist/chat/plugins/agent-hooks.d.ts +4 -0
  22. package/dist/chat/repository-instructions.d.ts +1 -1
  23. package/dist/chat/sandbox/paths.d.ts +1 -0
  24. package/dist/chat/sandbox/prepare-workspace.d.ts +11 -0
  25. package/dist/chat/sandbox/ref.d.ts +1 -0
  26. package/dist/chat/sandbox/sandbox.d.ts +4 -0
  27. package/dist/chat/sandbox/session.d.ts +4 -0
  28. package/dist/chat/sandbox/snapshot/profile.d.ts +4 -3
  29. package/dist/chat/sandbox/snapshot/resolve.d.ts +4 -0
  30. package/dist/chat/sandbox/workspace.d.ts +4 -0
  31. package/dist/chat/tools/types.d.ts +5 -0
  32. package/dist/chat/workspaces/checkout-path.d.ts +4 -0
  33. package/dist/chat/workspaces/store.d.ts +37 -0
  34. package/dist/chat/workspaces/tools.d.ts +4 -0
  35. package/dist/chat/workspaces/types.d.ts +13 -0
  36. package/dist/chat/workspaces/validation.d.ts +21 -0
  37. package/dist/{chunk-THXMSSFZ.js → chunk-26JYGL2L.js} +1 -1
  38. package/dist/{chunk-OPNJRQDP.js → chunk-2K25MRZK.js} +9 -6
  39. package/dist/{chunk-C3KUKA27.js → chunk-3L23VTY7.js} +17 -17
  40. package/dist/{chunk-JJ3VXFDI.js → chunk-4GTOFUQF.js} +1 -1
  41. package/dist/{chunk-RDV4BQRZ.js → chunk-5FWQ73UZ.js} +1 -1
  42. package/dist/{chunk-BPZQWAZJ.js → chunk-5GLGQHO3.js} +4 -1
  43. package/dist/{chunk-K7IV6UV3.js → chunk-6BXV63YX.js} +5 -5
  44. package/dist/{chunk-4WYGQ47U.js → chunk-6ENAACWN.js} +5 -5
  45. package/dist/{chunk-EHQLTK3X.js → chunk-7WKWMMYQ.js} +323 -127
  46. package/dist/chunk-BVREIWBD.js +131 -0
  47. package/dist/{chunk-DZC3F2UI.js → chunk-BXBZDV3C.js} +1 -1
  48. package/dist/{chunk-HW4BW3PC.js → chunk-CGCNFEAU.js} +4 -4
  49. package/dist/{chunk-JRVURR3H.js → chunk-FKDY7NTC.js} +543 -164
  50. package/dist/{chunk-MNCEGXEH.js → chunk-FX4PX3SA.js} +2 -2
  51. package/dist/{chunk-OBFRMQBY.js → chunk-H36GJVJV.js} +9 -9
  52. package/dist/{chunk-V7QL4QMW.js → chunk-HHW6ZVIZ.js} +12 -7
  53. package/dist/{chunk-O6P7DZ6O.js → chunk-JW23XD4I.js} +6 -6
  54. package/dist/{chunk-DDQ43S5Q.js → chunk-LPLVNMVN.js} +4 -4
  55. package/dist/{chunk-5NEBOHRG.js → chunk-NAGI2XH3.js} +4 -4
  56. package/dist/{chunk-FEWT5KPP.js → chunk-NLTQRYNP.js} +2 -2
  57. package/dist/{chunk-SA6BEEAB.js → chunk-PU45BSBS.js} +108 -74
  58. package/dist/{chunk-7DTPFFQX.js → chunk-RBV4JHCP.js} +1 -1
  59. package/dist/{chunk-6BJFIWEV.js → chunk-RJHZNPDH.js} +46 -87
  60. package/dist/{chunk-FESUWAYU.js → chunk-RULIISEI.js} +1 -1
  61. package/dist/{chunk-MMBMDSRK.js → chunk-RYYEHWIL.js} +1 -1
  62. package/dist/{chunk-Y75ZDQB5.js → chunk-VHSEMJTS.js} +1 -1
  63. package/dist/{chunk-5IDFPCON.js → chunk-VRS6WDK5.js} +1 -1
  64. package/dist/{chunk-EBEHJPFF.js → chunk-VV2DN24T.js} +1 -1
  65. package/dist/{chunk-WHAJ3QVY.js → chunk-WJUKESU2.js} +1 -1
  66. package/dist/{chunk-H2KW7AZC.js → chunk-XAIRI54J.js} +1 -1
  67. package/dist/{chunk-AJ7J2HWY.js → chunk-Y2B6AXZQ.js} +6 -6
  68. package/dist/{chunk-QHCIZLIX.js → chunk-YHCTPDXS.js} +356 -13
  69. package/dist/{chunk-CZ6EHOU5.js → chunk-ZSNABC2E.js} +40 -2
  70. package/dist/{chunk-KQSO4DJ4.js → chunk-ZUTAGCPH.js} +9 -9
  71. package/dist/cli/chat.js +32 -32
  72. package/dist/cli/check.js +6 -6
  73. package/dist/cli/plugins.js +13 -13
  74. package/dist/cli/snapshot-create.js +10 -10
  75. package/dist/cli/upgrade.js +7 -7
  76. package/dist/db/schema/workspaces.d.ts +169 -0
  77. package/dist/db/schema.d.ts +169 -1
  78. package/dist/{db-66XZ4HHQ.js → db-32KRFS6L.js} +7 -7
  79. package/dist/{experimental-HH6OM6UH.js → experimental-XVDOSRBS.js} +1 -1
  80. package/dist/instrumentation.js +4 -4
  81. package/dist/nitro.js +2 -2
  82. package/dist/{oauth-callback-server-FMLKDUUF.js → oauth-callback-server-CGZP2Z6U.js} +24 -24
  83. package/dist/{runner-YZONUMNY.js → runner-S5N4Q2XL.js} +32 -34
  84. package/dist/{skills-3UDJJXSO.js → skills-KYPSHSQR.js} +6 -6
  85. package/dist/spawn-EGOB3CLV.js +37 -0
  86. package/dist/{validation-3HTDBONC.js → validation-SZBE7JNF.js} +6 -6
  87. package/dist/version.js +1 -1
  88. package/dist/{work-O4YTIS54.js → work-GCZKHEZX.js} +18 -18
  89. package/dist/worker-6MY5Q4A2.js +24 -0
  90. package/migrations/0029_dizzy_barracuda.sql +19 -0
  91. package/migrations/meta/0029_snapshot.json +2709 -0
  92. package/migrations/meta/_journal.json +8 -1
  93. package/package.json +7 -4
  94. package/dist/chunk-EM225ITJ.js +0 -56
  95. package/dist/spawn-TBPVA5S2.js +0 -37
  96. package/dist/worker-WHURUEYX.js +0 -24
@@ -1,27 +1,31 @@
1
1
  import {
2
2
  getConversationEventStore,
3
- getDb
4
- } from "./chunk-DDQ43S5Q.js";
3
+ getDb,
4
+ getSqlExecutor
5
+ } from "./chunk-LPLVNMVN.js";
5
6
  import {
6
7
  juniorConversationAnnotations,
7
8
  juniorConversationEvents,
8
9
  juniorIdentities,
9
- juniorUsers
10
- } from "./chunk-CZ6EHOU5.js";
10
+ juniorUsers,
11
+ juniorWorkspaceRepos,
12
+ juniorWorkspaces
13
+ } from "./chunk-ZSNABC2E.js";
11
14
  import {
12
15
  SANDBOX_WORKSPACE_ROOT,
16
+ runNonInteractiveCommand,
13
17
  sleep
14
- } from "./chunk-EM225ITJ.js";
18
+ } from "./chunk-BVREIWBD.js";
15
19
  import {
16
20
  getStateAdapter
17
- } from "./chunk-DZC3F2UI.js";
21
+ } from "./chunk-BXBZDV3C.js";
18
22
  import {
19
23
  botConfig,
20
24
  completeObject,
21
25
  embedTexts,
22
26
  getSlackBotToken,
23
27
  standardModelId
24
- } from "./chunk-MNCEGXEH.js";
28
+ } from "./chunk-FX4PX3SA.js";
25
29
  import {
26
30
  isActorUserId,
27
31
  parseActorUserId,
@@ -37,7 +41,7 @@ import {
37
41
  setSpanAttributes,
38
42
  setSpanStatus,
39
43
  withSpan
40
- } from "./chunk-Y75ZDQB5.js";
44
+ } from "./chunk-VHSEMJTS.js";
41
45
 
42
46
  // src/chat/plugins/agent-hooks.ts
43
47
  import {
@@ -1196,6 +1200,275 @@ function resolveChannelCapabilities(channelId) {
1196
1200
 
1197
1201
  // src/chat/plugins/agent-hooks.ts
1198
1202
  import { z as z2 } from "zod";
1203
+
1204
+ // src/chat/workspaces/checkout-path.ts
1205
+ function tryWorkspaceRepoCheckoutPath(repo) {
1206
+ const name = repo.split("/").filter(Boolean).at(-1);
1207
+ if (!name || name === "." || name === ".." || !/^[A-Za-z0-9._-]+$/.test(name)) {
1208
+ return void 0;
1209
+ }
1210
+ return `repos/${name}`;
1211
+ }
1212
+ function workspaceRepoCheckoutPath(repo) {
1213
+ const path = tryWorkspaceRepoCheckoutPath(repo);
1214
+ if (!path) {
1215
+ throw new Error(`Invalid repository name for checkout path: ${repo}`);
1216
+ }
1217
+ return path;
1218
+ }
1219
+
1220
+ // src/chat/workspaces/store.ts
1221
+ import { randomUUID as randomUUID2 } from "crypto";
1222
+ import { and as and4, asc as asc2, eq as eq4, sql as sql2 } from "drizzle-orm";
1223
+
1224
+ // src/chat/workspaces/validation.ts
1225
+ var WORKSPACE_NAME_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
1226
+ var PROVIDER_PATTERN = /^[a-z][a-z0-9_-]{0,63}$/;
1227
+ var REPO_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
1228
+ var MAX_SETUP_SCRIPT_BYTES = 64 * 1024;
1229
+ var MAX_REPOS = 32;
1230
+ var WorkspaceValidationError = class extends Error {
1231
+ constructor(message) {
1232
+ super(message);
1233
+ this.name = "WorkspaceValidationError";
1234
+ }
1235
+ };
1236
+ function normalizeWorkspaceRecipe(input) {
1237
+ const name = input.name.trim().toLowerCase();
1238
+ if (!WORKSPACE_NAME_PATTERN.test(name)) {
1239
+ throw new WorkspaceValidationError(
1240
+ "Workspace name must start with a letter and use only lowercase letters, digits, underscores, or hyphens."
1241
+ );
1242
+ }
1243
+ const setupScript = input.setupScript ?? "";
1244
+ if (typeof setupScript !== "string") {
1245
+ throw new WorkspaceValidationError("Setup script must be a string.");
1246
+ }
1247
+ if (Buffer.byteLength(setupScript, "utf8") > MAX_SETUP_SCRIPT_BYTES) {
1248
+ throw new WorkspaceValidationError(
1249
+ `Setup script must be at most ${MAX_SETUP_SCRIPT_BYTES} bytes.`
1250
+ );
1251
+ }
1252
+ if (!Array.isArray(input.repos)) {
1253
+ throw new WorkspaceValidationError("Repositories must be an array.");
1254
+ }
1255
+ if (input.repos.length > MAX_REPOS) {
1256
+ throw new WorkspaceValidationError(
1257
+ `A Workspace can include at most ${MAX_REPOS} repositories.`
1258
+ );
1259
+ }
1260
+ const repos = input.repos.map((entry, index) => {
1261
+ const provider = entry.provider.trim().toLowerCase();
1262
+ if (!PROVIDER_PATTERN.test(provider)) {
1263
+ throw new WorkspaceValidationError(
1264
+ `Repository ${index + 1} provider is invalid.`
1265
+ );
1266
+ }
1267
+ const repo = entry.repo.trim();
1268
+ if (!REPO_PATTERN.test(repo)) {
1269
+ throw new WorkspaceValidationError(
1270
+ `Repository ${index + 1} must use owner/name format.`
1271
+ );
1272
+ }
1273
+ if (!tryWorkspaceRepoCheckoutPath(repo)) {
1274
+ throw new WorkspaceValidationError(
1275
+ `Repository ${index + 1} name cannot be used as a checkout path.`
1276
+ );
1277
+ }
1278
+ return {
1279
+ provider,
1280
+ repo,
1281
+ isPrimary: entry.isPrimary === true
1282
+ };
1283
+ });
1284
+ const identities = /* @__PURE__ */ new Set();
1285
+ for (const repo of repos) {
1286
+ const identity = `${repo.provider}:${repo.repo.toLowerCase()}`;
1287
+ if (identities.has(identity)) {
1288
+ throw new WorkspaceValidationError(
1289
+ `Duplicate repository in Workspace: ${repo.provider}:${repo.repo}`
1290
+ );
1291
+ }
1292
+ identities.add(identity);
1293
+ }
1294
+ const checkoutPaths = /* @__PURE__ */ new Set();
1295
+ for (const repo of repos) {
1296
+ const path = tryWorkspaceRepoCheckoutPath(repo.repo);
1297
+ const key = path.toLowerCase();
1298
+ if (checkoutPaths.has(key)) {
1299
+ throw new WorkspaceValidationError(
1300
+ `Workspace checkout path collision: ${path}`
1301
+ );
1302
+ }
1303
+ checkoutPaths.add(key);
1304
+ }
1305
+ const primaryCount = repos.filter((repo) => repo.isPrimary).length;
1306
+ if (repos.length > 0 && primaryCount === 0) {
1307
+ throw new WorkspaceValidationError(
1308
+ "Select one primary repository for AGENTS.md instructions."
1309
+ );
1310
+ }
1311
+ if (primaryCount > 1) {
1312
+ throw new WorkspaceValidationError(
1313
+ "A Workspace can have only one primary repository."
1314
+ );
1315
+ }
1316
+ return { name, setupScript, repos };
1317
+ }
1318
+
1319
+ // src/chat/workspaces/store.ts
1320
+ function workspaceFromRows(row, repos) {
1321
+ return {
1322
+ id: row.id,
1323
+ name: row.name,
1324
+ setupScript: row.setupScript,
1325
+ repos: repos.map((repo) => ({
1326
+ provider: repo.provider,
1327
+ repo: repo.repo,
1328
+ isPrimary: repo.isPrimary
1329
+ }))
1330
+ };
1331
+ }
1332
+ async function loadWorkspaceRepos(db, workspaceId) {
1333
+ return await db.select().from(juniorWorkspaceRepos).where(eq4(juniorWorkspaceRepos.workspaceId, workspaceId)).orderBy(
1334
+ asc2(juniorWorkspaceRepos.provider),
1335
+ asc2(juniorWorkspaceRepos.repo)
1336
+ );
1337
+ }
1338
+ async function replaceWorkspaceRepos(db, workspaceId, repos) {
1339
+ await db.delete(juniorWorkspaceRepos).where(eq4(juniorWorkspaceRepos.workspaceId, workspaceId));
1340
+ if (repos.length === 0) return;
1341
+ await db.insert(juniorWorkspaceRepos).values(
1342
+ repos.map((repo) => ({
1343
+ workspaceId,
1344
+ provider: repo.provider,
1345
+ repo: repo.repo,
1346
+ isPrimary: repo.isPrimary
1347
+ }))
1348
+ );
1349
+ }
1350
+ function isUniqueViolation(error) {
1351
+ let current = error;
1352
+ for (let depth = 0; current && depth < 6; depth += 1) {
1353
+ if (typeof current === "object" && current !== null && "code" in current && current.code === "23505") {
1354
+ return true;
1355
+ }
1356
+ current = typeof current === "object" && current !== null && "cause" in current ? current.cause : void 0;
1357
+ }
1358
+ return false;
1359
+ }
1360
+ async function listWorkspaces(db) {
1361
+ const [workspaces, repos] = await Promise.all([
1362
+ db.select().from(juniorWorkspaces).orderBy(asc2(juniorWorkspaces.name)),
1363
+ db.select().from(juniorWorkspaceRepos).orderBy(
1364
+ asc2(juniorWorkspaceRepos.workspaceId),
1365
+ asc2(juniorWorkspaceRepos.provider),
1366
+ asc2(juniorWorkspaceRepos.repo)
1367
+ )
1368
+ ]);
1369
+ return workspaces.map(
1370
+ (workspace) => workspaceFromRows(
1371
+ workspace,
1372
+ repos.filter((repo) => repo.workspaceId === workspace.id)
1373
+ )
1374
+ );
1375
+ }
1376
+ async function listWorkspaceNamesByRepository(db, input) {
1377
+ const provider = input.provider.trim().toLowerCase();
1378
+ const repo = input.repo.trim();
1379
+ const rows = await db.select({ name: juniorWorkspaces.name }).from(juniorWorkspaceRepos).innerJoin(
1380
+ juniorWorkspaces,
1381
+ eq4(juniorWorkspaces.id, juniorWorkspaceRepos.workspaceId)
1382
+ ).where(
1383
+ and4(
1384
+ eq4(juniorWorkspaceRepos.provider, provider),
1385
+ // Recipes store the provided repo casing; identity is case-insensitive.
1386
+ sql2`lower(${juniorWorkspaceRepos.repo}) = lower(${repo})`
1387
+ )
1388
+ ).orderBy(asc2(juniorWorkspaces.name));
1389
+ return rows.map((row) => row.name);
1390
+ }
1391
+ async function getWorkspaceByName(db, name) {
1392
+ const rows = await db.select().from(juniorWorkspaces).where(eq4(juniorWorkspaces.name, name)).limit(1);
1393
+ const workspace = rows[0];
1394
+ if (!workspace) return void 0;
1395
+ return workspaceFromRows(
1396
+ workspace,
1397
+ await loadWorkspaceRepos(db, workspace.id)
1398
+ );
1399
+ }
1400
+ async function getWorkspace(db, id) {
1401
+ const rows = await db.select().from(juniorWorkspaces).where(eq4(juniorWorkspaces.id, id)).limit(1);
1402
+ const workspace = rows[0];
1403
+ if (!workspace) return void 0;
1404
+ return workspaceFromRows(
1405
+ workspace,
1406
+ await loadWorkspaceRepos(db, workspace.id)
1407
+ );
1408
+ }
1409
+ async function createWorkspace(input) {
1410
+ const recipe = normalizeWorkspaceRecipe(input);
1411
+ const executor = getSqlExecutor();
1412
+ const now = /* @__PURE__ */ new Date();
1413
+ const id = randomUUID2();
1414
+ try {
1415
+ await executor.transaction(async () => {
1416
+ const db = executor.db();
1417
+ await db.insert(juniorWorkspaces).values({
1418
+ id,
1419
+ name: recipe.name,
1420
+ setupScript: recipe.setupScript,
1421
+ createdAt: now,
1422
+ updatedAt: now
1423
+ });
1424
+ await replaceWorkspaceRepos(db, id, recipe.repos);
1425
+ });
1426
+ } catch (error) {
1427
+ if (isUniqueViolation(error)) {
1428
+ throw new WorkspaceValidationError(
1429
+ `Workspace name already exists: ${recipe.name}`
1430
+ );
1431
+ }
1432
+ throw error;
1433
+ }
1434
+ return await getWorkspace(executor.db(), id);
1435
+ }
1436
+ async function updateWorkspace(id, input) {
1437
+ const recipe = normalizeWorkspaceRecipe(input);
1438
+ const executor = getSqlExecutor();
1439
+ const now = /* @__PURE__ */ new Date();
1440
+ try {
1441
+ const updated = await executor.transaction(async () => {
1442
+ const db = executor.db();
1443
+ const rows = await db.update(juniorWorkspaces).set({
1444
+ name: recipe.name,
1445
+ setupScript: recipe.setupScript,
1446
+ updatedAt: now
1447
+ }).where(eq4(juniorWorkspaces.id, id)).returning();
1448
+ if (!rows[0]) return void 0;
1449
+ await replaceWorkspaceRepos(db, id, recipe.repos);
1450
+ return rows[0];
1451
+ });
1452
+ if (!updated) return void 0;
1453
+ } catch (error) {
1454
+ if (isUniqueViolation(error)) {
1455
+ throw new WorkspaceValidationError(
1456
+ `Workspace name already exists: ${recipe.name}`
1457
+ );
1458
+ }
1459
+ throw error;
1460
+ }
1461
+ return await getWorkspace(executor.db(), id);
1462
+ }
1463
+ async function deleteWorkspace(id) {
1464
+ const executor = getSqlExecutor();
1465
+ return await executor.transaction(async () => {
1466
+ const rows = await executor.db().delete(juniorWorkspaces).where(eq4(juniorWorkspaces.id, id)).returning({ id: juniorWorkspaces.id });
1467
+ return rows.length > 0;
1468
+ });
1469
+ }
1470
+
1471
+ // src/chat/plugins/agent-hooks.ts
1199
1472
  var PluginHookDeniedError = class extends Error {
1200
1473
  constructor(message) {
1201
1474
  super(message);
@@ -1621,13 +1894,20 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
1621
1894
  resourceEvents,
1622
1895
  sandbox,
1623
1896
  state: createPluginState(pluginName),
1624
- users: { resolveActor }
1897
+ users: { resolveActor },
1898
+ workspaces: {
1899
+ async findByRepository(input) {
1900
+ return await listWorkspaceNamesByRepository(getDb(), input);
1901
+ }
1902
+ }
1625
1903
  };
1626
1904
  let pluginContext;
1627
1905
  switch (context.source.platform) {
1628
1906
  case "slack":
1629
1907
  if (context.destination.platform !== "slack") {
1630
- throw new TypeError("Slack plugin context requires Slack destination");
1908
+ throw new TypeError(
1909
+ "Slack plugin context requires Slack destination"
1910
+ );
1631
1911
  }
1632
1912
  pluginContext = {
1633
1913
  ...common,
@@ -1639,7 +1919,9 @@ function getPluginTools(context, sandbox = createSandboxCapability(context.works
1639
1919
  break;
1640
1920
  case "local":
1641
1921
  if (context.destination.platform !== "local") {
1642
- throw new TypeError("Local plugin context requires local destination");
1922
+ throw new TypeError(
1923
+ "Local plugin context requires local destination"
1924
+ );
1643
1925
  }
1644
1926
  pluginContext = {
1645
1927
  ...common,
@@ -2180,7 +2462,12 @@ function normalizeEnv(value) {
2180
2462
  }
2181
2463
  return env;
2182
2464
  }
2183
- function createSandboxCapability(workspace) {
2465
+ function preparationSignal(inputSignal, ownerSignal) {
2466
+ if (!inputSignal) return ownerSignal;
2467
+ if (!ownerSignal) return inputSignal;
2468
+ return AbortSignal.any([inputSignal, ownerSignal]);
2469
+ }
2470
+ function createSandboxCapability(workspace, ownerSignal) {
2184
2471
  return {
2185
2472
  root: SANDBOX_WORKSPACE_ROOT,
2186
2473
  juniorRoot: `${SANDBOX_WORKSPACE_ROOT}/.junior`,
@@ -2188,7 +2475,11 @@ function createSandboxCapability(workspace) {
2188
2475
  return await workspace.readFileToBuffer({ path: filePath }) ?? null;
2189
2476
  },
2190
2477
  async run(input) {
2191
- const result = await workspace.runCommand(input);
2478
+ const signal = preparationSignal(input.signal, ownerSignal);
2479
+ const result = await runNonInteractiveCommand(workspace, {
2480
+ ...input,
2481
+ ...signal ? { signal } : {}
2482
+ });
2192
2483
  return {
2193
2484
  exitCode: result.exitCode,
2194
2485
  stdout: result.stdout,
@@ -2243,6 +2534,49 @@ function createPluginHookRunner(input = {}) {
2243
2534
  }
2244
2535
  }
2245
2536
  },
2537
+ async prepareWorkspace(sandbox, repos, signal) {
2538
+ const preparers = new Set(
2539
+ loaded.filter((plugin) => plugin.hooks?.workspacePrepare).map((plugin) => plugin.manifest.name)
2540
+ );
2541
+ const unhandledProviders = [
2542
+ ...new Set(
2543
+ repos.map((repo) => repo.provider).filter((provider) => !preparers.has(provider))
2544
+ )
2545
+ ].sort();
2546
+ if (unhandledProviders.length > 0) {
2547
+ throw new Error(
2548
+ `Workspace repository providers have no preparation hook: ${unhandledProviders.join(", ")}`
2549
+ );
2550
+ }
2551
+ const selectedRepos = repos.map((repo) => ({
2552
+ provider: repo.provider,
2553
+ repo: repo.repo,
2554
+ path: workspaceRepoCheckoutPath(repo.repo)
2555
+ }));
2556
+ const paths = /* @__PURE__ */ new Set();
2557
+ for (const entry of selectedRepos) {
2558
+ const key = entry.path.toLowerCase();
2559
+ if (paths.has(key)) {
2560
+ throw new Error(`Workspace checkout path collision: ${entry.path}`);
2561
+ }
2562
+ paths.add(key);
2563
+ }
2564
+ const sandboxCapability = createSandboxCapability(sandbox, signal);
2565
+ for (const plugin of loaded) {
2566
+ const hook = plugin.hooks?.workspacePrepare;
2567
+ if (!hook) continue;
2568
+ const selected = selectedRepos.filter((repo) => repo.provider === plugin.manifest.name).map((repo) => ({
2569
+ path: repo.path,
2570
+ repo: repo.repo
2571
+ }));
2572
+ if (selected.length === 0) continue;
2573
+ await hook({
2574
+ ...basePluginContext(plugin),
2575
+ repos: selected,
2576
+ sandbox: sandboxCapability
2577
+ });
2578
+ }
2579
+ },
2246
2580
  async prepareSandbox(sandbox) {
2247
2581
  const sandboxCapability = createSandboxCapability(sandbox);
2248
2582
  for (const plugin of loaded) {
@@ -2356,6 +2690,15 @@ export {
2356
2690
  bindEventTaskCredentialSubject,
2357
2691
  verifyEventTaskCredentialSubject,
2358
2692
  resolveChannelCapabilities,
2693
+ tryWorkspaceRepoCheckoutPath,
2694
+ workspaceRepoCheckoutPath,
2695
+ WorkspaceValidationError,
2696
+ listWorkspaces,
2697
+ getWorkspaceByName,
2698
+ getWorkspace,
2699
+ createWorkspace,
2700
+ updateWorkspace,
2701
+ deleteWorkspace,
2359
2702
  PluginHookDeniedError,
2360
2703
  validatePlugins,
2361
2704
  setPlugins,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DEFAULT_SQL_STATEMENT_TIMEOUT_MS
3
- } from "./chunk-MNCEGXEH.js";
3
+ } from "./chunk-FX4PX3SA.js";
4
4
  import {
5
5
  juniorDestinations,
6
6
  timestamptz
@@ -645,6 +645,40 @@ var juniorSchedulerRuns = pgTable16(
645
645
  ]
646
646
  );
647
647
 
648
+ // src/db/schema/workspaces.ts
649
+ import { sql as sql6 } from "drizzle-orm";
650
+ import {
651
+ boolean as boolean3,
652
+ pgTable as pgTable17,
653
+ primaryKey as primaryKey9,
654
+ text as text17,
655
+ uniqueIndex as uniqueIndex7
656
+ } from "drizzle-orm/pg-core";
657
+ var juniorWorkspaces = pgTable17(
658
+ "junior_workspaces",
659
+ {
660
+ id: text17("id").primaryKey(),
661
+ name: text17("name").notNull(),
662
+ setupScript: text17("setup_script").notNull().default(""),
663
+ createdAt: timestamptz("created_at").notNull(),
664
+ updatedAt: timestamptz("updated_at").notNull()
665
+ },
666
+ (table) => [uniqueIndex7("junior_workspaces_name_idx").on(table.name)]
667
+ );
668
+ var juniorWorkspaceRepos = pgTable17(
669
+ "junior_workspace_repos",
670
+ {
671
+ workspaceId: text17("workspace_id").notNull().references(() => juniorWorkspaces.id, { onDelete: "cascade" }),
672
+ provider: text17("provider").notNull(),
673
+ repo: text17("repo").notNull(),
674
+ isPrimary: boolean3("is_primary").notNull().default(false)
675
+ },
676
+ (table) => [
677
+ primaryKey9({ columns: [table.workspaceId, table.provider, table.repo] }),
678
+ uniqueIndex7("junior_workspace_repos_primary_idx").on(table.workspaceId).where(sql6`${table.isPrimary}`)
679
+ ]
680
+ );
681
+
648
682
  // src/db/schema.ts
649
683
  var juniorSqlSchema = {
650
684
  juniorArtifacts,
@@ -665,7 +699,9 @@ var juniorSqlSchema = {
665
699
  juniorTaskExecutions,
666
700
  juniorSchedulerRuns,
667
701
  juniorSchedulerTasks,
668
- juniorUsers
702
+ juniorUsers,
703
+ juniorWorkspaceRepos,
704
+ juniorWorkspaces
669
705
  };
670
706
 
671
707
  // src/db/neon.ts
@@ -937,5 +973,7 @@ export {
937
973
  juniorTaskExecutions,
938
974
  juniorSchedulerTasks,
939
975
  juniorSchedulerRuns,
976
+ juniorWorkspaces,
977
+ juniorWorkspaceRepos,
940
978
  createJuniorSqlExecutor
941
979
  };
@@ -2,7 +2,7 @@ import {
2
2
  coerceThreadConversationState,
3
3
  getPersistedThreadState,
4
4
  persistThreadStateById
5
- } from "./chunk-OPNJRQDP.js";
5
+ } from "./chunk-2K25MRZK.js";
6
6
  import {
7
7
  pluginTaskId,
8
8
  pluginTaskParamsSchema,
@@ -22,7 +22,7 @@ import {
22
22
  markTurnCompleted,
23
23
  normalizeToolNameFromResult,
24
24
  stripRuntimeTurnContext
25
- } from "./chunk-V7QL4QMW.js";
25
+ } from "./chunk-HHW6ZVIZ.js";
26
26
  import {
27
27
  JUNIOR_THREAD_STATE_TTL_MS
28
28
  } from "./chunk-3Y47VMHV.js";
@@ -33,25 +33,25 @@ import {
33
33
  createPluginModel,
34
34
  createPluginState,
35
35
  getPlugins
36
- } from "./chunk-QHCIZLIX.js";
36
+ } from "./chunk-YHCTPDXS.js";
37
37
  import {
38
38
  getConversationStore,
39
39
  getDb,
40
40
  sameActorIdentity
41
- } from "./chunk-DDQ43S5Q.js";
41
+ } from "./chunk-LPLVNMVN.js";
42
42
  import {
43
43
  juniorConversations,
44
44
  juniorTaskExecutions
45
- } from "./chunk-CZ6EHOU5.js";
45
+ } from "./chunk-ZSNABC2E.js";
46
46
  import {
47
47
  getStateAdapter
48
- } from "./chunk-DZC3F2UI.js";
48
+ } from "./chunk-BXBZDV3C.js";
49
49
  import {
50
50
  ProviderError,
51
51
  findProviderError,
52
52
  getProviderErrorAttributes,
53
53
  getProviderErrorUserMessage
54
- } from "./chunk-MNCEGXEH.js";
54
+ } from "./chunk-FX4PX3SA.js";
55
55
  import {
56
56
  credentialSubjectSchema
57
57
  } from "./chunk-BDUENQD3.js";
@@ -61,7 +61,7 @@ import {
61
61
  import {
62
62
  buildTurnFailureResponse,
63
63
  logWarn
64
- } from "./chunk-Y75ZDQB5.js";
64
+ } from "./chunk-VHSEMJTS.js";
65
65
 
66
66
  // src/chat/runtime/delivered-turn-state.ts
67
67
  function buildDeliveredTurnStatePatch(args) {
@@ -87,7 +87,7 @@ import { pluginRunContextSchema } from "@sentry/junior-plugin-api";
87
87
 
88
88
  // src/chat/services/turn-session-routing.ts
89
89
  async function resolveTurnSessionRouting(args) {
90
- const conversationStore = args.conversationStore ?? (await import("./db-66XZ4HHQ.js")).getConversationStore();
90
+ const conversationStore = args.conversationStore ?? (await import("./db-32KRFS6L.js")).getConversationStore();
91
91
  const conversation = await conversationStore.get({
92
92
  conversationId: args.conversationId
93
93
  });
package/dist/cli/chat.js CHANGED
@@ -1,44 +1,44 @@
1
1
  import {
2
2
  executeAgentRun
3
- } from "../chunk-EHQLTK3X.js";
4
- import "../chunk-K7IV6UV3.js";
3
+ } from "../chunk-7WKWMMYQ.js";
4
+ import "../chunk-6BXV63YX.js";
5
5
  import "../chunk-DIB4GX3G.js";
6
6
  import {
7
7
  createAgentRunner
8
- } from "../chunk-OBFRMQBY.js";
8
+ } from "../chunk-H36GJVJV.js";
9
9
  import "../chunk-RF6J3DIO.js";
10
- import "../chunk-O6P7DZ6O.js";
10
+ import "../chunk-JW23XD4I.js";
11
11
  import "../chunk-7MA4HV4W.js";
12
- import "../chunk-5IDFPCON.js";
13
- import "../chunk-6BJFIWEV.js";
14
- import "../chunk-4WYGQ47U.js";
15
- import "../chunk-WHAJ3QVY.js";
16
- import "../chunk-V7QL4QMW.js";
12
+ import "../chunk-VRS6WDK5.js";
13
+ import "../chunk-RJHZNPDH.js";
14
+ import "../chunk-6ENAACWN.js";
15
+ import "../chunk-WJUKESU2.js";
16
+ import "../chunk-HHW6ZVIZ.js";
17
17
  import "../chunk-3Y47VMHV.js";
18
18
  import "../chunk-PDO5BLNM.js";
19
19
  import "../chunk-UIE3R5XU.js";
20
20
  import {
21
21
  loadAppPluginSet
22
22
  } from "../chunk-Y2CM7HXH.js";
23
- import "../chunk-QHCIZLIX.js";
24
- import "../chunk-DDQ43S5Q.js";
25
- import "../chunk-CZ6EHOU5.js";
26
- import "../chunk-EM225ITJ.js";
27
- import "../chunk-DZC3F2UI.js";
28
- import "../chunk-MNCEGXEH.js";
23
+ import "../chunk-YHCTPDXS.js";
24
+ import "../chunk-LPLVNMVN.js";
25
+ import "../chunk-ZSNABC2E.js";
26
+ import "../chunk-BVREIWBD.js";
27
+ import "../chunk-BXBZDV3C.js";
28
+ import "../chunk-FX4PX3SA.js";
29
29
  import "../chunk-VFUK3X5B.js";
30
- import "../chunk-7DTPFFQX.js";
31
- import "../chunk-FEWT5KPP.js";
32
- import "../chunk-FESUWAYU.js";
33
- import "../chunk-EBEHJPFF.js";
30
+ import "../chunk-RBV4JHCP.js";
31
+ import "../chunk-NLTQRYNP.js";
32
+ import "../chunk-RULIISEI.js";
33
+ import "../chunk-VV2DN24T.js";
34
34
  import "../chunk-BDUENQD3.js";
35
35
  import "../chunk-V7YE4WCP.js";
36
- import "../chunk-Y75ZDQB5.js";
36
+ import "../chunk-VHSEMJTS.js";
37
37
  import "../chunk-6GWA276C.js";
38
38
  import "../chunk-OOUNJNMR.js";
39
39
  import "../chunk-H4HBKISO.js";
40
- import "../chunk-THXMSSFZ.js";
41
- import "../chunk-BPZQWAZJ.js";
40
+ import "../chunk-26JYGL2L.js";
41
+ import "../chunk-5GLGQHO3.js";
42
42
  import "../chunk-MLKGABMK.js";
43
43
 
44
44
  // src/cli/chat.ts
@@ -161,10 +161,10 @@ async function configureLocalChatPlugins(pluginSet) {
161
161
  databaseModule
162
162
  ] = await Promise.all([
163
163
  import("../plugins-HW4VWW6G.js"),
164
- import("../agent-hooks-TL2SGC5W.js"),
165
- import("../catalog-runtime-IGSMIADJ.js"),
166
- import("../validation-3HTDBONC.js"),
167
- import("../db-66XZ4HHQ.js")
164
+ import("../agent-hooks-WTBPNRTC.js"),
165
+ import("../catalog-runtime-CZ2SQINY.js"),
166
+ import("../validation-SZBE7JNF.js"),
167
+ import("../db-32KRFS6L.js")
168
168
  ]);
169
169
  const resolvedPluginSet = pluginSet === void 0 ? await loadLocalPluginSet() : pluginSet ?? void 0;
170
170
  const plugins = pluginsModule.pluginRuntimeRegistrationsFromPluginSet(resolvedPluginSet);
@@ -228,16 +228,16 @@ function localDevServerUrl() {
228
228
  async function prepareLocalChatRun(io, pluginSet) {
229
229
  defaultStateAdapterForLocalChat();
230
230
  await configureLocalChatPlugins(pluginSet);
231
- const { setExperimentalFeatures } = await import("../experimental-HH6OM6UH.js");
231
+ const { setExperimentalFeatures } = await import("../experimental-XVDOSRBS.js");
232
232
  setExperimentalFeatures({ subagents: true });
233
- const { runLocalAgentTurn } = await import("../runner-YZONUMNY.js");
234
- const { startLocalOAuthCallbackServer } = await import("../oauth-callback-server-FMLKDUUF.js");
233
+ const { runLocalAgentTurn } = await import("../runner-S5N4Q2XL.js");
234
+ const { startLocalOAuthCallbackServer } = await import("../oauth-callback-server-CGZP2Z6U.js");
235
235
  const { createLocalOAuthState } = await import("../oauth-relay-3ZU72UGL.js");
236
236
  const { createLocalSandboxEgressSignalTransport } = await import("../sandbox-egress-signals-U3ZX7C5W.js");
237
- const { bindSpawnAgent } = await import("../spawn-TBPVA5S2.js");
238
- const { createAgentInvocationWorker, routeAgentInvocationWork } = await import("../work-O4YTIS54.js");
237
+ const { bindSpawnAgent } = await import("../spawn-EGOB3CLV.js");
238
+ const { createAgentInvocationWorker, routeAgentInvocationWork } = await import("../work-GCZKHEZX.js");
239
239
  const { createLocalConversationWork } = await import("../conversation-work-KATFHLG3.js");
240
- const { processConversationWork } = await import("../worker-WHURUEYX.js");
240
+ const { processConversationWork } = await import("../worker-6MY5Q4A2.js");
241
241
  let agentRunner;
242
242
  const localConversationWork = createLocalConversationWork(async (message) => {
243
243
  if (!agentRunner) {