@granular-software/sdk 0.4.51 → 0.4.52

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.
@@ -1,5 +1,5 @@
1
- import { a as EnvironmentSession, G as Granular } from './client-djorlOpn.mjs';
2
- import { G as GranularSpendContext, h as OpenAITokenSpend, P as Prompt, bV as RecordObjectOptions, cJ as ManifestContent, c as SessionHeapSnapshot, T as ToolWithHandler, J as ConnectOptions, af as CreateEnvironmentData, y as GranularOptions } from './spend-BA-jZwZ0.mjs';
1
+ import { a as EnvironmentSession, G as Granular } from './client-B2OyGOHE.mjs';
2
+ import { G as GranularSpendContext, h as OpenAITokenSpend, P as Prompt, bW as RecordObjectOptions, cL as ManifestContent, c as SessionHeapSnapshot, T as ToolWithHandler, K as ConnectOptions, ag as CreateEnvironmentData, y as GranularOptions } from './spend-DpRRCrAr.mjs';
3
3
  import { GranularAgentToolInfo, GeneratedJobCodeIssue, BuildGranularAgentSystemPromptInput, HarnessRenderedPrompt, HarnessRenderedContinuation, HarnessControllerBudgets } from './agent-harness.mjs';
4
4
  import '@automerge/automerge';
5
5
  import '@automerge/automerge/slim';
@@ -1,5 +1,5 @@
1
- import { a as EnvironmentSession, G as Granular } from './client-NSH-tpNU.js';
2
- import { G as GranularSpendContext, h as OpenAITokenSpend, P as Prompt, bV as RecordObjectOptions, cJ as ManifestContent, c as SessionHeapSnapshot, T as ToolWithHandler, J as ConnectOptions, af as CreateEnvironmentData, y as GranularOptions } from './spend-BA-jZwZ0.js';
1
+ import { a as EnvironmentSession, G as Granular } from './client-BMfjRJTs.js';
2
+ import { G as GranularSpendContext, h as OpenAITokenSpend, P as Prompt, bW as RecordObjectOptions, cL as ManifestContent, c as SessionHeapSnapshot, T as ToolWithHandler, K as ConnectOptions, ag as CreateEnvironmentData, y as GranularOptions } from './spend-DpRRCrAr.js';
3
3
  import { GranularAgentToolInfo, GeneratedJobCodeIssue, BuildGranularAgentSystemPromptInput, HarnessRenderedPrompt, HarnessRenderedContinuation, HarnessControllerBudgets } from './agent-harness.js';
4
4
  import '@automerge/automerge';
5
5
  import '@automerge/automerge/slim';
@@ -14218,7 +14218,8 @@ function normalizeEnvironmentSetupSummary(setup) {
14218
14218
  environmentId: String(setup.environmentId || ""),
14219
14219
  sandboxId: String(setup.sandboxId || ""),
14220
14220
  subjectId: String(setup.subjectId || ""),
14221
- triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : "new_environment",
14221
+ triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : setup.triggerReason === "explicit_reset" ? "explicit_reset" : "new_environment",
14222
+ operationKey: typeof setup.operationKey === "string" ? setup.operationKey : null,
14222
14223
  lifecycleStatus: setup.lifecycleStatus === "completed" || setup.lifecycleStatus === "failed" ? setup.lifecycleStatus : "running",
14223
14224
  stage: typeof setup.stage === "string" ? setup.stage : null,
14224
14225
  totalObjectsToImport: Number(setup.totalObjectsToImport || 0),
@@ -15500,6 +15501,7 @@ var Environment = class _Environment {
15500
15501
  records: recordsToImport,
15501
15502
  batchSize: options.batchSize,
15502
15503
  setupRunId: options.setupRunId,
15504
+ operationKey: options.operationKey,
15503
15505
  writeMode: options.writeMode
15504
15506
  })
15505
15507
  }
@@ -16304,6 +16306,107 @@ var Granular = class _Granular {
16304
16306
  await this.maybeRunEnvironmentImporter(resolved, environment);
16305
16307
  return environment;
16306
16308
  }
16309
+ /**
16310
+ * Read the active environment selected by Granular for an already-recorded
16311
+ * external user. This is intentionally read-only: browser/login code must
16312
+ * not create subjects or environments as a side effect.
16313
+ */
16314
+ async getActiveEnvironmentForUser(options) {
16315
+ const sandboxId = options.sandboxId.trim();
16316
+ const tagName = options.tag.trim();
16317
+ const userId = options.userId.trim();
16318
+ if (!sandboxId || !tagName || !userId) {
16319
+ throw new Error(
16320
+ "getActiveEnvironmentForUser() requires sandboxId, tag, and userId."
16321
+ );
16322
+ }
16323
+ const subjects = await this.request(
16324
+ `/control/subjects?identityId=${encodeURIComponent(userId)}`
16325
+ );
16326
+ const subject = (subjects.items || []).find(
16327
+ (item) => item.identityId === userId || item.userId === userId
16328
+ );
16329
+ if (!subject?.subjectId && !subject?.granularId) {
16330
+ return null;
16331
+ }
16332
+ const subjectId = subject.subjectId || subject.granularId;
16333
+ const tags = await this.request(
16334
+ `/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`
16335
+ );
16336
+ const tag = (tags.items || []).find(
16337
+ (item) => item?.name === tagName
16338
+ );
16339
+ if (!tag) return null;
16340
+ const query = new URLSearchParams({
16341
+ tagId: tag.tagId,
16342
+ slot: options.slot?.trim() || "default"
16343
+ });
16344
+ try {
16345
+ const payload = await this.request(
16346
+ `/control/sandboxes/${encodeURIComponent(sandboxId)}/subjects/${encodeURIComponent(subjectId)}/active-environment?${query.toString()}`
16347
+ );
16348
+ return payload.environment ? this.bindEnvironmentHandle(
16349
+ normalizeEnvironmentData(payload.environment)
16350
+ ) : null;
16351
+ } catch (error) {
16352
+ const message = error instanceof Error ? error.message : String(error);
16353
+ if (message.includes("404") || message.includes("not found")) {
16354
+ return null;
16355
+ }
16356
+ throw error;
16357
+ }
16358
+ }
16359
+ /**
16360
+ * Register one reviewed, pre-existing environment as the active workspace
16361
+ * for an external user. This is for a controlled migration only: it does
16362
+ * not create an environment and it does not run an importer.
16363
+ */
16364
+ async adoptEnvironmentForUser(options) {
16365
+ const sandboxId = options.sandboxId.trim();
16366
+ const tagName = options.tag.trim();
16367
+ const userId = options.userId.trim();
16368
+ const environmentId = options.environmentId.trim();
16369
+ if (!sandboxId || !tagName || !userId || !environmentId) {
16370
+ throw new Error(
16371
+ "adoptEnvironmentForUser() requires sandboxId, tag, userId, and environmentId."
16372
+ );
16373
+ }
16374
+ const subjects = await this.request(
16375
+ `/control/subjects?identityId=${encodeURIComponent(userId)}`
16376
+ );
16377
+ const subject = (subjects.items || []).find(
16378
+ (item) => item.identityId === userId || item.userId === userId
16379
+ );
16380
+ if (!subject?.subjectId && !subject?.granularId) {
16381
+ throw new Error(`No Granular subject exists for user ${userId}.`);
16382
+ }
16383
+ const tags = await this.request(`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`);
16384
+ const tag = (tags.items || []).find(
16385
+ (item) => item?.name === tagName
16386
+ );
16387
+ if (!tag) {
16388
+ throw new Error(`Tag ${tagName} was not found in sandbox ${sandboxId}.`);
16389
+ }
16390
+ const payload = await this.request(
16391
+ "/control/environment-activations/adopt",
16392
+ {
16393
+ method: "POST",
16394
+ body: JSON.stringify({
16395
+ environmentId,
16396
+ subjectId: subject.subjectId || subject.granularId,
16397
+ tagId: tag.tagId,
16398
+ slot: options.slot?.trim() || "default",
16399
+ confirmExistingData: true
16400
+ })
16401
+ }
16402
+ );
16403
+ if (!payload.environment) {
16404
+ throw new Error("Granular did not return the adopted environment.");
16405
+ }
16406
+ return this.bindEnvironmentHandle(
16407
+ normalizeEnvironmentData(payload.environment)
16408
+ );
16409
+ }
16307
16410
  /**
16308
16411
  * Deprecated compatibility alias for `openEnvironment()`.
16309
16412
  *
@@ -16335,7 +16438,9 @@ var Granular = class _Granular {
16335
16438
  requestedOntology,
16336
16439
  sandboxId: environmentData.sandboxId,
16337
16440
  subjectId: environmentData.subjectId,
16338
- setupTriggerReason: options.reason || "new_environment"
16441
+ externalUserId: environmentData.subjectId,
16442
+ setupTriggerReason: options.reason || "new_environment",
16443
+ setupOperationKey: options.operationKey
16339
16444
  },
16340
16445
  environment
16341
16446
  );
@@ -16347,20 +16452,17 @@ var Granular = class _Granular {
16347
16452
  }
16348
16453
  return tag;
16349
16454
  }
16350
- buildManagedEnvironmentName(tag, versionId) {
16351
- return `__sdk__${tag}__${versionId}__pinned`;
16455
+ buildManagedEnvironmentName(tag, versionId, resetKey) {
16456
+ if (!resetKey) {
16457
+ return `__sdk__${tag}__${versionId}__tracked`;
16458
+ }
16459
+ const safeResetKey = resetKey.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
16460
+ return `__sdk__${tag}__${versionId}__reset__${safeResetKey}`;
16352
16461
  }
16353
16462
  isManagedEnvironmentName(environment, tagName) {
16354
16463
  const name = environment.environment || environment.envName || "";
16355
16464
  return name.startsWith(`__sdk__${tagName}__`);
16356
16465
  }
16357
- isPinnedToVersion(environment, versionId) {
16358
- return environment.buildPolicy.mode === "pinned" && (environment.versionId === versionId || environment.buildPolicy.versionId === versionId || environment.buildPolicy.buildId === versionId);
16359
- }
16360
- matchesTagTrackedEnvironment(environment, tagName, tagId) {
16361
- const environmentTagName = environment.tag?.name || environment.buildPolicy.tagName || null;
16362
- return environment.tagId === tagId || environmentTagName === tagName || environment.environment === tagName || environment.envName === tagName || environment.environment === this.buildManagedEnvironmentName(tagName, environment.versionId) || environment.envName === this.buildManagedEnvironmentName(tagName, environment.versionId);
16363
- }
16364
16466
  sortEnvironmentsByRecency(environments) {
16365
16467
  return [...environments].sort(
16366
16468
  (left, right) => right.updatedAt - left.updatedAt
@@ -16410,48 +16512,119 @@ var Granular = class _Granular {
16410
16512
  `Tag "${tagName}" does not currently point to a build/version.`
16411
16513
  );
16412
16514
  }
16515
+ const slot = options.slot?.trim() || "default";
16516
+ const resetKey = options.resetKey?.trim() || void 0;
16517
+ const resolveActive = async (operationKey) => {
16518
+ const query = new URLSearchParams({ tagId: tag.tagId, slot });
16519
+ if (operationKey) query.set("operationKey", operationKey);
16520
+ try {
16521
+ const payload = await this.request(
16522
+ `/control/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/subjects/${encodeURIComponent(user.granularId)}/active-environment?${query.toString()}`
16523
+ );
16524
+ return payload.environment ? normalizeEnvironmentData(payload.environment) : null;
16525
+ } catch (error) {
16526
+ const message = error instanceof Error ? error.message : String(error);
16527
+ if (message.includes("404") || message.includes("not found")) {
16528
+ return null;
16529
+ }
16530
+ throw error;
16531
+ }
16532
+ };
16533
+ const activate = async (environment2) => {
16534
+ const payload = await this.request(
16535
+ "/control/environment-activations",
16536
+ {
16537
+ method: "POST",
16538
+ body: JSON.stringify({
16539
+ environmentId: environment2.environmentId,
16540
+ tagId: tag.tagId,
16541
+ slot,
16542
+ operationKey: resetKey,
16543
+ operation: resetKey ? "explicit_reset" : void 0
16544
+ })
16545
+ }
16546
+ );
16547
+ return normalizeEnvironmentData(payload.environment);
16548
+ };
16549
+ if (resetKey) {
16550
+ const resetEnvironment = await resolveActive(resetKey);
16551
+ if (resetEnvironment) {
16552
+ return {
16553
+ environment: resetEnvironment,
16554
+ requestedOntology: ontology,
16555
+ sandboxId: sandbox.sandboxId,
16556
+ subjectId: user.granularId,
16557
+ externalUserId: user.userId,
16558
+ // Retrying an explicit reset must also resume its durable setup run.
16559
+ // Otherwise a Container crash after queue submission would leave a
16560
+ // valid environment permanently marked as "running".
16561
+ setupTriggerReason: "explicit_reset",
16562
+ setupOperationKey: resetKey
16563
+ };
16564
+ }
16565
+ } else {
16566
+ const active = await resolveActive();
16567
+ if (active && (active.versionId === targetVersionId || options.createFreshIfOutdated !== true)) {
16568
+ return {
16569
+ environment: active,
16570
+ requestedOntology: ontology,
16571
+ sandboxId: sandbox.sandboxId,
16572
+ subjectId: user.granularId,
16573
+ externalUserId: user.userId
16574
+ };
16575
+ }
16576
+ }
16413
16577
  const allEnvironments = await this.environments.list(sandbox.sandboxId);
16414
16578
  const userEnvironments = allEnvironments.filter(
16415
- (environment) => environment.subjectId === user.granularId
16579
+ (environment2) => environment2.subjectId === user.granularId
16416
16580
  );
16417
16581
  const currentMatches = this.sortEnvironmentsByRecency(
16418
16582
  userEnvironments.filter(
16419
- (environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId) && environment.versionId === targetVersionId && (!this.isManagedEnvironmentName(environment, tagName) || this.isPinnedToVersion(environment, targetVersionId))
16583
+ (environment2) => environment2.tagId === tag.tagId && environment2.versionId === targetVersionId
16420
16584
  )
16421
16585
  );
16422
- if (currentMatches.length > 0) {
16586
+ if (!resetKey && currentMatches.length > 0) {
16587
+ const environment2 = await activate(currentMatches[0]);
16423
16588
  return {
16424
- environment: currentMatches[0],
16589
+ environment: environment2,
16425
16590
  requestedOntology: ontology,
16426
16591
  sandboxId: sandbox.sandboxId,
16427
- subjectId: user.granularId
16592
+ subjectId: user.granularId,
16593
+ externalUserId: user.userId
16428
16594
  };
16429
16595
  }
16430
16596
  const outdatedMatches = this.sortEnvironmentsByRecency(
16431
- userEnvironments.filter(
16432
- (environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
16433
- )
16597
+ userEnvironments.filter((environment2) => environment2.tagId === tag.tagId)
16434
16598
  );
16435
16599
  if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
16600
+ const environment2 = await activate(outdatedMatches[0]);
16436
16601
  return {
16437
- environment: outdatedMatches[0],
16602
+ environment: environment2,
16438
16603
  requestedOntology: ontology,
16439
16604
  sandboxId: sandbox.sandboxId,
16440
- subjectId: user.granularId
16605
+ subjectId: user.granularId,
16606
+ externalUserId: user.userId
16441
16607
  };
16442
16608
  }
16609
+ const created = await this.environments.create(sandbox.sandboxId, {
16610
+ subjectId: user.granularId,
16611
+ environment: this.buildManagedEnvironmentName(
16612
+ tagName,
16613
+ targetVersionId,
16614
+ resetKey
16615
+ ),
16616
+ tagId: tag.tagId,
16617
+ permissionProfileId: null
16618
+ });
16619
+ const environment = await activate(created);
16443
16620
  return {
16444
- environment: await this.environments.create(sandbox.sandboxId, {
16445
- subjectId: user.granularId,
16446
- environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
16447
- tagId: tag.tagId,
16448
- versionId: targetVersionId,
16449
- permissionProfileId: null
16450
- }),
16621
+ environment,
16451
16622
  requestedOntology: ontology,
16452
16623
  sandboxId: sandbox.sandboxId,
16453
16624
  subjectId: user.granularId,
16454
- setupTriggerReason: outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment"
16625
+ externalUserId: user.userId,
16626
+ setupTriggerReason: resetKey ? "explicit_reset" : outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment",
16627
+ setupOperationKey: resetKey
16455
16628
  };
16456
16629
  }
16457
16630
  /**
@@ -16730,11 +16903,24 @@ var Granular = class _Granular {
16730
16903
  {
16731
16904
  method: "POST",
16732
16905
  body: JSON.stringify({
16733
- triggerReason: resolved.setupTriggerReason
16906
+ triggerReason: resolved.setupTriggerReason,
16907
+ operationKey: resolved.setupOperationKey
16734
16908
  })
16735
16909
  }
16736
16910
  );
16737
16911
  const setupRunId = setupRun.setupRunId;
16912
+ let claim = null;
16913
+ for (let attempt = 0; attempt < 3; attempt += 1) {
16914
+ claim = await this.request(
16915
+ `/control/environment-setup-runs/${setupRunId}/importer-claim`,
16916
+ { method: "POST", body: JSON.stringify({}) }
16917
+ );
16918
+ if (claim.action !== "busy") break;
16919
+ await sleep(Math.min(3e4, Math.max(250, claim.retryAfterMs || 1e3)));
16920
+ }
16921
+ if (!claim) {
16922
+ throw new Error(`Unable to claim environment setup run ${setupRunId}.`);
16923
+ }
16738
16924
  const updateSetupRun = async (patch) => {
16739
16925
  await this.request(
16740
16926
  `/control/environment-setup-runs/${setupRunId}`,
@@ -16744,10 +16930,26 @@ var Granular = class _Granular {
16744
16930
  }
16745
16931
  );
16746
16932
  };
16933
+ if (claim.action === "submitted") {
16934
+ const completedSetupRun = await this.request(
16935
+ `/control/environment-setup-runs/${setupRunId}`,
16936
+ { method: "PATCH", body: JSON.stringify({ markHookCompleted: true }) }
16937
+ );
16938
+ const refreshedEnvironment = await this.environments.get(
16939
+ environment.environmentId
16940
+ );
16941
+ environment.syncEnvironmentData(refreshedEnvironment);
16942
+ return completedSetupRun;
16943
+ }
16944
+ if (claim.action === "busy" || claim.action === "terminal") {
16945
+ return claim.summary;
16946
+ }
16947
+ let importSequence = 0;
16747
16948
  const importerContext = {
16748
16949
  environmentId: environment.environmentId,
16749
16950
  sandboxId: environment.sandboxId,
16750
16951
  subjectId: environment.subjectId,
16952
+ externalUserId: resolved.externalUserId,
16751
16953
  reason: resolved.setupTriggerReason,
16752
16954
  incrementTotalObjectsToImportCount: async (n) => {
16753
16955
  const safeIncrement = Math.max(0, Math.trunc(n));
@@ -16764,7 +16966,10 @@ var Granular = class _Granular {
16764
16966
  importRecords: async (records, options) => environment.enqueueRecordImport(records, {
16765
16967
  batchSize: options?.batchSize,
16766
16968
  writeMode: options?.writeMode,
16767
- setupRunId
16969
+ setupRunId,
16970
+ // Sequence is deterministic for a retry of one importer hook. It
16971
+ // prevents a Container restart from creating a second queue import.
16972
+ operationKey: `${setupRunId}:import:${importSequence++}`
16768
16973
  })
16769
16974
  };
16770
16975
  try {