@granular-software/sdk 0.4.51 → 0.4.53

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-CfTDojJF.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-DcRzwuGp.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,156 @@ 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
+ * Resolve one exact environment only when Granular confirms that it belongs
16361
+ * to the given external user in the requested sandbox (and, optionally,
16362
+ * tag). This is deliberately read-only: callers use it to keep an already
16363
+ * opened delegated workspace stable while a newer active environment is
16364
+ * being prepared in the background.
16365
+ */
16366
+ async getEnvironmentForUser(options) {
16367
+ const sandboxId = options.sandboxId.trim();
16368
+ const userId = options.userId.trim();
16369
+ const environmentId = options.environmentId.trim();
16370
+ const tagName = options.tag?.trim();
16371
+ if (!sandboxId || !userId || !environmentId) {
16372
+ throw new Error(
16373
+ "getEnvironmentForUser() requires sandboxId, userId, and environmentId."
16374
+ );
16375
+ }
16376
+ const subjects = await this.request(
16377
+ `/control/subjects?identityId=${encodeURIComponent(userId)}`
16378
+ );
16379
+ const subject = (subjects.items || []).find(
16380
+ (item) => item.identityId === userId || item.userId === userId
16381
+ );
16382
+ const subjectId = subject?.subjectId || subject?.granularId;
16383
+ if (!subjectId) return null;
16384
+ let environment;
16385
+ try {
16386
+ environment = await this.environments.get(environmentId);
16387
+ } catch (error) {
16388
+ const message = error instanceof Error ? error.message : String(error);
16389
+ if (message.includes("404") || message.includes("not found")) {
16390
+ return null;
16391
+ }
16392
+ throw error;
16393
+ }
16394
+ if (environment.sandboxId !== sandboxId || environment.subjectId !== subjectId) {
16395
+ return null;
16396
+ }
16397
+ if (tagName) {
16398
+ const tags = await this.request(`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`);
16399
+ const tag = (tags.items || []).find(
16400
+ (candidate) => candidate?.name === tagName
16401
+ );
16402
+ if (!tag || environment.tagId !== tag.tagId) {
16403
+ return null;
16404
+ }
16405
+ }
16406
+ return this.bindEnvironmentHandle(environment);
16407
+ }
16408
+ /**
16409
+ * Register one reviewed, pre-existing environment as the active workspace
16410
+ * for an external user. This is for a controlled migration only: it does
16411
+ * not create an environment and it does not run an importer.
16412
+ */
16413
+ async adoptEnvironmentForUser(options) {
16414
+ const sandboxId = options.sandboxId.trim();
16415
+ const tagName = options.tag.trim();
16416
+ const userId = options.userId.trim();
16417
+ const environmentId = options.environmentId.trim();
16418
+ if (!sandboxId || !tagName || !userId || !environmentId) {
16419
+ throw new Error(
16420
+ "adoptEnvironmentForUser() requires sandboxId, tag, userId, and environmentId."
16421
+ );
16422
+ }
16423
+ const subjects = await this.request(
16424
+ `/control/subjects?identityId=${encodeURIComponent(userId)}`
16425
+ );
16426
+ const subject = (subjects.items || []).find(
16427
+ (item) => item.identityId === userId || item.userId === userId
16428
+ );
16429
+ if (!subject?.subjectId && !subject?.granularId) {
16430
+ throw new Error(`No Granular subject exists for user ${userId}.`);
16431
+ }
16432
+ const tags = await this.request(`/control/sandboxes/${encodeURIComponent(sandboxId)}/tags`);
16433
+ const tag = (tags.items || []).find(
16434
+ (item) => item?.name === tagName
16435
+ );
16436
+ if (!tag) {
16437
+ throw new Error(`Tag ${tagName} was not found in sandbox ${sandboxId}.`);
16438
+ }
16439
+ const payload = await this.request(
16440
+ "/control/environment-activations/adopt",
16441
+ {
16442
+ method: "POST",
16443
+ body: JSON.stringify({
16444
+ environmentId,
16445
+ subjectId: subject.subjectId || subject.granularId,
16446
+ tagId: tag.tagId,
16447
+ slot: options.slot?.trim() || "default",
16448
+ confirmExistingData: true
16449
+ })
16450
+ }
16451
+ );
16452
+ if (!payload.environment) {
16453
+ throw new Error("Granular did not return the adopted environment.");
16454
+ }
16455
+ return this.bindEnvironmentHandle(
16456
+ normalizeEnvironmentData(payload.environment)
16457
+ );
16458
+ }
16307
16459
  /**
16308
16460
  * Deprecated compatibility alias for `openEnvironment()`.
16309
16461
  *
@@ -16335,7 +16487,9 @@ var Granular = class _Granular {
16335
16487
  requestedOntology,
16336
16488
  sandboxId: environmentData.sandboxId,
16337
16489
  subjectId: environmentData.subjectId,
16338
- setupTriggerReason: options.reason || "new_environment"
16490
+ externalUserId: environmentData.subjectId,
16491
+ setupTriggerReason: options.reason || "new_environment",
16492
+ setupOperationKey: options.operationKey
16339
16493
  },
16340
16494
  environment
16341
16495
  );
@@ -16347,20 +16501,17 @@ var Granular = class _Granular {
16347
16501
  }
16348
16502
  return tag;
16349
16503
  }
16350
- buildManagedEnvironmentName(tag, versionId) {
16351
- return `__sdk__${tag}__${versionId}__pinned`;
16504
+ buildManagedEnvironmentName(tag, versionId, resetKey) {
16505
+ if (!resetKey) {
16506
+ return `__sdk__${tag}__${versionId}__tracked`;
16507
+ }
16508
+ const safeResetKey = resetKey.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80);
16509
+ return `__sdk__${tag}__${versionId}__reset__${safeResetKey}`;
16352
16510
  }
16353
16511
  isManagedEnvironmentName(environment, tagName) {
16354
16512
  const name = environment.environment || environment.envName || "";
16355
16513
  return name.startsWith(`__sdk__${tagName}__`);
16356
16514
  }
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
16515
  sortEnvironmentsByRecency(environments) {
16365
16516
  return [...environments].sort(
16366
16517
  (left, right) => right.updatedAt - left.updatedAt
@@ -16410,48 +16561,119 @@ var Granular = class _Granular {
16410
16561
  `Tag "${tagName}" does not currently point to a build/version.`
16411
16562
  );
16412
16563
  }
16564
+ const slot = options.slot?.trim() || "default";
16565
+ const resetKey = options.resetKey?.trim() || void 0;
16566
+ const resolveActive = async (operationKey) => {
16567
+ const query = new URLSearchParams({ tagId: tag.tagId, slot });
16568
+ if (operationKey) query.set("operationKey", operationKey);
16569
+ try {
16570
+ const payload = await this.request(
16571
+ `/control/sandboxes/${encodeURIComponent(sandbox.sandboxId)}/subjects/${encodeURIComponent(user.granularId)}/active-environment?${query.toString()}`
16572
+ );
16573
+ return payload.environment ? normalizeEnvironmentData(payload.environment) : null;
16574
+ } catch (error) {
16575
+ const message = error instanceof Error ? error.message : String(error);
16576
+ if (message.includes("404") || message.includes("not found")) {
16577
+ return null;
16578
+ }
16579
+ throw error;
16580
+ }
16581
+ };
16582
+ const activate = async (environment2) => {
16583
+ const payload = await this.request(
16584
+ "/control/environment-activations",
16585
+ {
16586
+ method: "POST",
16587
+ body: JSON.stringify({
16588
+ environmentId: environment2.environmentId,
16589
+ tagId: tag.tagId,
16590
+ slot,
16591
+ operationKey: resetKey,
16592
+ operation: resetKey ? "explicit_reset" : void 0
16593
+ })
16594
+ }
16595
+ );
16596
+ return normalizeEnvironmentData(payload.environment);
16597
+ };
16598
+ if (resetKey) {
16599
+ const resetEnvironment = await resolveActive(resetKey);
16600
+ if (resetEnvironment) {
16601
+ return {
16602
+ environment: resetEnvironment,
16603
+ requestedOntology: ontology,
16604
+ sandboxId: sandbox.sandboxId,
16605
+ subjectId: user.granularId,
16606
+ externalUserId: user.userId,
16607
+ // Retrying an explicit reset must also resume its durable setup run.
16608
+ // Otherwise a Container crash after queue submission would leave a
16609
+ // valid environment permanently marked as "running".
16610
+ setupTriggerReason: "explicit_reset",
16611
+ setupOperationKey: resetKey
16612
+ };
16613
+ }
16614
+ } else {
16615
+ const active = await resolveActive();
16616
+ if (active && (active.versionId === targetVersionId || options.createFreshIfOutdated !== true)) {
16617
+ return {
16618
+ environment: active,
16619
+ requestedOntology: ontology,
16620
+ sandboxId: sandbox.sandboxId,
16621
+ subjectId: user.granularId,
16622
+ externalUserId: user.userId
16623
+ };
16624
+ }
16625
+ }
16413
16626
  const allEnvironments = await this.environments.list(sandbox.sandboxId);
16414
16627
  const userEnvironments = allEnvironments.filter(
16415
- (environment) => environment.subjectId === user.granularId
16628
+ (environment2) => environment2.subjectId === user.granularId
16416
16629
  );
16417
16630
  const currentMatches = this.sortEnvironmentsByRecency(
16418
16631
  userEnvironments.filter(
16419
- (environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId) && environment.versionId === targetVersionId && (!this.isManagedEnvironmentName(environment, tagName) || this.isPinnedToVersion(environment, targetVersionId))
16632
+ (environment2) => environment2.tagId === tag.tagId && environment2.versionId === targetVersionId
16420
16633
  )
16421
16634
  );
16422
- if (currentMatches.length > 0) {
16635
+ if (!resetKey && currentMatches.length > 0) {
16636
+ const environment2 = await activate(currentMatches[0]);
16423
16637
  return {
16424
- environment: currentMatches[0],
16638
+ environment: environment2,
16425
16639
  requestedOntology: ontology,
16426
16640
  sandboxId: sandbox.sandboxId,
16427
- subjectId: user.granularId
16641
+ subjectId: user.granularId,
16642
+ externalUserId: user.userId
16428
16643
  };
16429
16644
  }
16430
16645
  const outdatedMatches = this.sortEnvironmentsByRecency(
16431
- userEnvironments.filter(
16432
- (environment) => this.matchesTagTrackedEnvironment(environment, tagName, tag.tagId)
16433
- )
16646
+ userEnvironments.filter((environment2) => environment2.tagId === tag.tagId)
16434
16647
  );
16435
16648
  if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
16649
+ const environment2 = await activate(outdatedMatches[0]);
16436
16650
  return {
16437
- environment: outdatedMatches[0],
16651
+ environment: environment2,
16438
16652
  requestedOntology: ontology,
16439
16653
  sandboxId: sandbox.sandboxId,
16440
- subjectId: user.granularId
16654
+ subjectId: user.granularId,
16655
+ externalUserId: user.userId
16441
16656
  };
16442
16657
  }
16658
+ const created = await this.environments.create(sandbox.sandboxId, {
16659
+ subjectId: user.granularId,
16660
+ environment: this.buildManagedEnvironmentName(
16661
+ tagName,
16662
+ targetVersionId,
16663
+ resetKey
16664
+ ),
16665
+ tagId: tag.tagId,
16666
+ permissionProfileId: null
16667
+ });
16668
+ const environment = await activate(created);
16443
16669
  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
- }),
16670
+ environment,
16451
16671
  requestedOntology: ontology,
16452
16672
  sandboxId: sandbox.sandboxId,
16453
16673
  subjectId: user.granularId,
16454
- setupTriggerReason: outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment"
16674
+ externalUserId: user.userId,
16675
+ setupTriggerReason: resetKey ? "explicit_reset" : outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment",
16676
+ setupOperationKey: resetKey
16455
16677
  };
16456
16678
  }
16457
16679
  /**
@@ -16730,11 +16952,24 @@ var Granular = class _Granular {
16730
16952
  {
16731
16953
  method: "POST",
16732
16954
  body: JSON.stringify({
16733
- triggerReason: resolved.setupTriggerReason
16955
+ triggerReason: resolved.setupTriggerReason,
16956
+ operationKey: resolved.setupOperationKey
16734
16957
  })
16735
16958
  }
16736
16959
  );
16737
16960
  const setupRunId = setupRun.setupRunId;
16961
+ let claim = null;
16962
+ for (let attempt = 0; attempt < 3; attempt += 1) {
16963
+ claim = await this.request(
16964
+ `/control/environment-setup-runs/${setupRunId}/importer-claim`,
16965
+ { method: "POST", body: JSON.stringify({}) }
16966
+ );
16967
+ if (claim.action !== "busy") break;
16968
+ await sleep(Math.min(3e4, Math.max(250, claim.retryAfterMs || 1e3)));
16969
+ }
16970
+ if (!claim) {
16971
+ throw new Error(`Unable to claim environment setup run ${setupRunId}.`);
16972
+ }
16738
16973
  const updateSetupRun = async (patch) => {
16739
16974
  await this.request(
16740
16975
  `/control/environment-setup-runs/${setupRunId}`,
@@ -16744,10 +16979,26 @@ var Granular = class _Granular {
16744
16979
  }
16745
16980
  );
16746
16981
  };
16982
+ if (claim.action === "submitted") {
16983
+ const completedSetupRun = await this.request(
16984
+ `/control/environment-setup-runs/${setupRunId}`,
16985
+ { method: "PATCH", body: JSON.stringify({ markHookCompleted: true }) }
16986
+ );
16987
+ const refreshedEnvironment = await this.environments.get(
16988
+ environment.environmentId
16989
+ );
16990
+ environment.syncEnvironmentData(refreshedEnvironment);
16991
+ return completedSetupRun;
16992
+ }
16993
+ if (claim.action === "busy" || claim.action === "terminal") {
16994
+ return claim.summary;
16995
+ }
16996
+ let importSequence = 0;
16747
16997
  const importerContext = {
16748
16998
  environmentId: environment.environmentId,
16749
16999
  sandboxId: environment.sandboxId,
16750
17000
  subjectId: environment.subjectId,
17001
+ externalUserId: resolved.externalUserId,
16751
17002
  reason: resolved.setupTriggerReason,
16752
17003
  incrementTotalObjectsToImportCount: async (n) => {
16753
17004
  const safeIncrement = Math.max(0, Math.trunc(n));
@@ -16764,7 +17015,10 @@ var Granular = class _Granular {
16764
17015
  importRecords: async (records, options) => environment.enqueueRecordImport(records, {
16765
17016
  batchSize: options?.batchSize,
16766
17017
  writeMode: options?.writeMode,
16767
- setupRunId
17018
+ setupRunId,
17019
+ // Sequence is deterministic for a retry of one importer hook. It
17020
+ // prevents a Container restart from creating a second queue import.
17021
+ operationKey: `${setupRunId}:import:${importSequence++}`
16768
17022
  })
16769
17023
  };
16770
17024
  try {