@granular-software/sdk 0.4.34 → 0.4.35

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,4 +1,4 @@
1
- import { P as Prompt, f as EnvironmentSession, c as SessionHeapSnapshot, bg as ManifestContent, aO as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, J as CreateEnvironmentData, k as GranularOptions } from './client-iw76FL_8.mjs';
1
+ import { P as Prompt, f as EnvironmentSession, c as SessionHeapSnapshot, bl as ManifestContent, aO as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, J as CreateEnvironmentData, k as GranularOptions } from './client-Cq8onk2D.mjs';
2
2
  import { GeneratedJobCodeIssue, HarnessControllerBudgets } from './agent-harness.mjs';
3
3
  import '@automerge/automerge';
4
4
  import '@automerge/automerge/slim';
@@ -1,4 +1,4 @@
1
- import { P as Prompt, f as EnvironmentSession, c as SessionHeapSnapshot, bg as ManifestContent, aO as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, J as CreateEnvironmentData, k as GranularOptions } from './client-iw76FL_8.js';
1
+ import { P as Prompt, f as EnvironmentSession, c as SessionHeapSnapshot, bl as ManifestContent, aO as RecordObjectOptions, T as ToolWithHandler, G as Granular, C as ConnectOptions, J as CreateEnvironmentData, k as GranularOptions } from './client-Cq8onk2D.js';
2
2
  import { GeneratedJobCodeIssue, HarnessControllerBudgets } from './agent-harness.js';
3
3
  import '@automerge/automerge';
4
4
  import '@automerge/automerge/slim';
@@ -12377,6 +12377,38 @@ function normalizeUser(user) {
12377
12377
  permissions: Array.isArray(user.permissions) ? user.permissions : []
12378
12378
  };
12379
12379
  }
12380
+ function normalizeEnvironmentSetupSummary(setup) {
12381
+ if (!setup) {
12382
+ return null;
12383
+ }
12384
+ const queuedRecords = Number(setup.queuedRecords || 0);
12385
+ const processingRecords = Number(setup.processingRecords || 0);
12386
+ return {
12387
+ ...setup,
12388
+ setupRunId: String(setup.setupRunId || ""),
12389
+ environmentId: String(setup.environmentId || ""),
12390
+ sandboxId: String(setup.sandboxId || ""),
12391
+ subjectId: String(setup.subjectId || ""),
12392
+ triggerReason: setup.triggerReason === "fresh_after_version_update" ? "fresh_after_version_update" : "new_environment",
12393
+ lifecycleStatus: setup.lifecycleStatus === "completed" || setup.lifecycleStatus === "failed" ? setup.lifecycleStatus : "running",
12394
+ stage: typeof setup.stage === "string" ? setup.stage : null,
12395
+ totalObjectsToImport: Number(setup.totalObjectsToImport || 0),
12396
+ totalImports: Number(setup.totalImports || 0),
12397
+ activeImports: Number(setup.activeImports || 0),
12398
+ totalRecords: Number(setup.totalRecords || 0),
12399
+ queuedRecords,
12400
+ processingRecords,
12401
+ completedRecords: Number(setup.completedRecords || 0),
12402
+ failedRecords: Number(setup.failedRecords || 0),
12403
+ canceledRecords: Number(setup.canceledRecords || 0),
12404
+ awaitingRecords: Number(setup.awaitingRecords || 0) || queuedRecords + processingRecords,
12405
+ errorMessage: typeof setup.errorMessage === "string" ? setup.errorMessage : null,
12406
+ startedAt: Number(setup.startedAt || Date.now()),
12407
+ hookCompletedAt: setup.hookCompletedAt == null ? null : Number(setup.hookCompletedAt),
12408
+ finishedAt: setup.finishedAt == null ? null : Number(setup.finishedAt),
12409
+ updatedAt: Number(setup.updatedAt || Date.now())
12410
+ };
12411
+ }
12380
12412
  function normalizeEnvironmentData(environment) {
12381
12413
  const buildPolicy = environment.buildPolicy || environment.tracking || (environment.tagId ? { mode: "tag", tagId: environment.tagId } : {
12382
12414
  mode: "pinned",
@@ -12390,7 +12422,8 @@ function normalizeEnvironmentData(environment) {
12390
12422
  envName: environmentName,
12391
12423
  environment: environmentName,
12392
12424
  buildPolicy,
12393
- tracking: environment.tracking || buildPolicy
12425
+ tracking: environment.tracking || buildPolicy,
12426
+ setup: normalizeEnvironmentSetupSummary(environment.setup)
12394
12427
  };
12395
12428
  }
12396
12429
  var Environment = class {
@@ -12448,6 +12481,10 @@ var Environment = class {
12448
12481
  get updateState() {
12449
12482
  return this.envData.updateState;
12450
12483
  }
12484
+ /** The latest setup/import run summary for this environment, when available. */
12485
+ get setup() {
12486
+ return this.envData.setup || null;
12487
+ }
12451
12488
  /** Convenience flag for whether this environment trails the current tag target */
12452
12489
  get isOutdated() {
12453
12490
  return this.envData.updateState === "update_available";
@@ -12468,6 +12505,9 @@ var Environment = class {
12468
12505
  get runtimeBaseUrl() {
12469
12506
  return this.getRuntimeBaseUrl();
12470
12507
  }
12508
+ syncEnvironmentData(envData) {
12509
+ this.envData = normalizeEnvironmentData(envData);
12510
+ }
12471
12511
  get sessions() {
12472
12512
  return {
12473
12513
  list: async (options) => this.listSessions(options?.status || "active"),
@@ -13413,7 +13453,8 @@ var Environment = class {
13413
13453
  method: "POST",
13414
13454
  body: JSON.stringify({
13415
13455
  records,
13416
- batchSize: options.batchSize
13456
+ batchSize: options.batchSize,
13457
+ setupRunId: options.setupRunId
13417
13458
  })
13418
13459
  }
13419
13460
  );
@@ -13561,18 +13602,12 @@ var EnvironmentSession = class extends Session {
13561
13602
  }
13562
13603
  get messages() {
13563
13604
  return {
13564
- list: (options = {}) => this.sessionDataRequest(
13565
- "/messages",
13566
- options
13567
- )
13605
+ list: (options = {}) => this.sessionDataRequest("/messages", options)
13568
13606
  };
13569
13607
  }
13570
13608
  get timeline() {
13571
13609
  return {
13572
- list: (options = {}) => this.sessionDataRequest(
13573
- "/timeline",
13574
- options
13575
- )
13610
+ list: (options = {}) => this.sessionDataRequest("/timeline", options)
13576
13611
  };
13577
13612
  }
13578
13613
  get jobs() {
@@ -13589,10 +13624,7 @@ var EnvironmentSession = class extends Session {
13589
13624
  get heap() {
13590
13625
  return {
13591
13626
  entries: {
13592
- list: (options = {}) => this.sessionDataRequest(
13593
- "/heap/entries",
13594
- options
13595
- ),
13627
+ list: (options = {}) => this.sessionDataRequest("/heap/entries", options),
13596
13628
  get: (path2) => this.sessionDataRequest(
13597
13629
  `/heap/entries/${encodeURIComponent(path2)}`
13598
13630
  )
@@ -13636,10 +13668,7 @@ var EnvironmentSession = class extends Session {
13636
13668
  const heap = normalizeHeapSnapshot({
13637
13669
  entriesByPath: Object.fromEntries(
13638
13670
  entries.map((entry) => {
13639
- return entry?.path ? [
13640
- entry.path,
13641
- entry
13642
- ] : null;
13671
+ return entry?.path ? [entry.path, entry] : null;
13643
13672
  }).filter(
13644
13673
  (entry) => Boolean(entry)
13645
13674
  )
@@ -13805,6 +13834,15 @@ var OntologyHandle = class {
13805
13834
  disconnect: async () => this.granular.disconnectEffects(this.ontologyNameOrId)
13806
13835
  };
13807
13836
  }
13837
+ get importer() {
13838
+ return {
13839
+ onEnvironmentCreate: (handler) => this.granular.registerEnvironmentImporter(
13840
+ this.ontologyNameOrId,
13841
+ handler
13842
+ ),
13843
+ clear: () => this.granular.clearEnvironmentImporter(this.ontologyNameOrId)
13844
+ };
13845
+ }
13808
13846
  };
13809
13847
  var Granular = class _Granular {
13810
13848
  apiKey;
@@ -13821,6 +13859,10 @@ var Granular = class _Granular {
13821
13859
  sandboxEffectHosts = /* @__PURE__ */ new Map();
13822
13860
  /** In-flight host connection promises to avoid duplicate concurrent connects */
13823
13861
  sandboxEffectHostPromises = /* @__PURE__ */ new Map();
13862
+ /** Ontology-bound environment importer hooks keyed by the caller's ontology identifier. */
13863
+ ontologyImporters = /* @__PURE__ */ new Map();
13864
+ /** Resolved importer hooks keyed by sandboxId for fast lookups during openEnvironment(). */
13865
+ sandboxImporters = /* @__PURE__ */ new Map();
13824
13866
  /**
13825
13867
  * Create a new Granular client
13826
13868
  * @param options - Client configuration
@@ -13846,6 +13888,18 @@ var Granular = class _Granular {
13846
13888
  ontology(ontologyNameOrId) {
13847
13889
  return new OntologyHandle(this, ontologyNameOrId);
13848
13890
  }
13891
+ registerEnvironmentImporter(ontologyNameOrId, handler) {
13892
+ this.ontologyImporters.set(ontologyNameOrId, handler);
13893
+ if (ontologyNameOrId.startsWith("sbx_")) {
13894
+ this.sandboxImporters.set(ontologyNameOrId, handler);
13895
+ }
13896
+ }
13897
+ clearEnvironmentImporter(ontologyNameOrId) {
13898
+ this.ontologyImporters.delete(ontologyNameOrId);
13899
+ if (ontologyNameOrId.startsWith("sbx_")) {
13900
+ this.sandboxImporters.delete(ontologyNameOrId);
13901
+ }
13902
+ }
13849
13903
  /**
13850
13904
  * Records/upserts a user and prepares them for sandbox connections
13851
13905
  *
@@ -13962,11 +14016,13 @@ var Granular = class _Granular {
13962
14016
  * ```
13963
14017
  */
13964
14018
  async openEnvironment(options) {
13965
- const envData = await this.resolveOpenEnvironmentData(
14019
+ const resolved = await this.resolveOpenEnvironmentData(
13966
14020
  options,
13967
14021
  "openEnvironment"
13968
14022
  );
13969
- return this.bindEnvironmentHandle(envData);
14023
+ const environment = this.bindEnvironmentHandle(resolved.environment);
14024
+ await this.maybeRunEnvironmentImporter(resolved, environment);
14025
+ return environment;
13970
14026
  }
13971
14027
  /**
13972
14028
  * Deprecated compatibility alias for `openEnvironment()`.
@@ -14053,7 +14109,12 @@ var Granular = class _Granular {
14053
14109
  )
14054
14110
  );
14055
14111
  if (currentMatches.length > 0) {
14056
- return currentMatches[0];
14112
+ return {
14113
+ environment: currentMatches[0],
14114
+ requestedOntology: ontology,
14115
+ sandboxId: sandbox.sandboxId,
14116
+ subjectId: user.granularId
14117
+ };
14057
14118
  }
14058
14119
  const outdatedMatches = this.sortEnvironmentsByRecency(
14059
14120
  userEnvironments.filter(
@@ -14061,14 +14122,25 @@ var Granular = class _Granular {
14061
14122
  )
14062
14123
  );
14063
14124
  if (outdatedMatches.length > 0 && options.createFreshIfOutdated !== true) {
14064
- return outdatedMatches[0];
14125
+ return {
14126
+ environment: outdatedMatches[0],
14127
+ requestedOntology: ontology,
14128
+ sandboxId: sandbox.sandboxId,
14129
+ subjectId: user.granularId
14130
+ };
14065
14131
  }
14066
- return this.environments.create(sandbox.sandboxId, {
14132
+ return {
14133
+ environment: await this.environments.create(sandbox.sandboxId, {
14134
+ subjectId: user.granularId,
14135
+ environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
14136
+ tagId: tag.tagId,
14137
+ permissionProfileId: null
14138
+ }),
14139
+ requestedOntology: ontology,
14140
+ sandboxId: sandbox.sandboxId,
14067
14141
  subjectId: user.granularId,
14068
- environment: this.buildManagedEnvironmentName(tagName, targetVersionId),
14069
- tagId: tag.tagId,
14070
- permissionProfileId: null
14071
- });
14142
+ setupTriggerReason: outdatedMatches.length > 0 ? "fresh_after_version_update" : "new_environment"
14143
+ };
14072
14144
  }
14073
14145
  /**
14074
14146
  * List active (open) sessions for an environment — each session is one agent conversation thread.
@@ -14185,6 +14257,80 @@ var Granular = class _Granular {
14185
14257
  });
14186
14258
  return this.connectSession({ sessionId, clientId: options?.clientId });
14187
14259
  }
14260
+ resolveEnvironmentImporter(requestedOntology, sandboxId) {
14261
+ const resolved = this.sandboxImporters.get(sandboxId) || this.ontologyImporters.get(requestedOntology);
14262
+ if (resolved && !this.sandboxImporters.has(sandboxId) && this.ontologyImporters.get(requestedOntology) === resolved) {
14263
+ this.sandboxImporters.set(sandboxId, resolved);
14264
+ }
14265
+ return resolved;
14266
+ }
14267
+ async maybeRunEnvironmentImporter(resolved, environment) {
14268
+ if (!resolved.setupTriggerReason) {
14269
+ return;
14270
+ }
14271
+ const importer = this.resolveEnvironmentImporter(
14272
+ resolved.requestedOntology,
14273
+ resolved.sandboxId
14274
+ );
14275
+ if (!importer) {
14276
+ return;
14277
+ }
14278
+ const setupRun = await this.request(
14279
+ `/control/environments/${environment.environmentId}/setup-runs`,
14280
+ {
14281
+ method: "POST",
14282
+ body: JSON.stringify({
14283
+ triggerReason: resolved.setupTriggerReason
14284
+ })
14285
+ }
14286
+ );
14287
+ const setupRunId = setupRun.setupRunId;
14288
+ const updateSetupRun = async (patch) => {
14289
+ await this.request(
14290
+ `/control/environment-setup-runs/${setupRunId}`,
14291
+ {
14292
+ method: "PATCH",
14293
+ body: JSON.stringify(patch)
14294
+ }
14295
+ );
14296
+ };
14297
+ const importerContext = {
14298
+ environmentId: environment.environmentId,
14299
+ sandboxId: environment.sandboxId,
14300
+ subjectId: environment.subjectId,
14301
+ reason: resolved.setupTriggerReason,
14302
+ incrementTotalObjectsToImportCount: async (n) => {
14303
+ const safeIncrement = Math.max(0, Math.trunc(n));
14304
+ if (safeIncrement <= 0) {
14305
+ return;
14306
+ }
14307
+ await updateSetupRun({
14308
+ incrementTotalObjectsToImportCount: safeIncrement
14309
+ });
14310
+ },
14311
+ setStage: async (stage) => {
14312
+ await updateSetupRun({ stage });
14313
+ },
14314
+ importRecords: async (records, options) => environment.enqueueRecordImport(records, {
14315
+ batchSize: options?.batchSize,
14316
+ setupRunId
14317
+ })
14318
+ };
14319
+ try {
14320
+ await importer(importerContext);
14321
+ await updateSetupRun({ markHookCompleted: true });
14322
+ const refreshedEnvironment = await this.environments.get(
14323
+ environment.environmentId
14324
+ );
14325
+ environment.syncEnvironmentData(refreshedEnvironment);
14326
+ } catch (error) {
14327
+ await updateSetupRun({
14328
+ status: "failed",
14329
+ errorMessage: error instanceof Error ? error.message : String(error)
14330
+ }).catch(() => void 0);
14331
+ throw error;
14332
+ }
14333
+ }
14188
14334
  bindEnvironmentHandle(envData) {
14189
14335
  const graphqlEndpoint = `${this.httpUrl}/orchestrator/graphql`;
14190
14336
  return new Environment(this, envData, this.apiKey, graphqlEndpoint);
@@ -15646,12 +15792,11 @@ function buildContinuationInstruction(resultPreview) {
15646
15792
  "Continue the same user request using the latest structured session state.",
15647
15793
  "Take only the minimum next step that directly helps the user.",
15648
15794
  "Use the active tasks, decisions, prompts, and heap references as the source of truth instead of replaying old work.",
15649
- "If the user names a concrete record that is not already in the heap, fetch it from the graph instead of replying that it is not in context.",
15795
+ "If the user names a concrete record that is not already in the heap, resolve it from the graph before saying it is missing: try a broad search, then a small set of normalized/fuzzy variants or a paged scan when the domain supports it.",
15796
+ "If the request needs all matching records, use iterate(...) or page until hasMore is false. A single list(...) or page(...) call is only one page.",
15650
15797
  "If this request clearly spans multiple steps and there are no active tasks yet, create 2-4 short user-visible tasks now.",
15651
15798
  "Reuse any existing taskId and decisionId values exactly as they appear in AGENT LOOP STATE.",
15652
- "Use ask_user with type input for open-ended preferences or missing text. Use type choice only for a short explicit shortlist.",
15653
- "Do not ask for confirmation in plain text. Use loop.confirm(...) when approval is needed.",
15654
- "Await loop.ask_user(...) and loop.confirm(...). Those helpers pause the current job and resume it after the user answers.",
15799
+ "When progress depends on the user's choice, missing detail, or approval, use loop.ask_user(...) or loop.confirm(...) so the job pauses and resumes through the live workflow.",
15655
15800
  "After a resumed ask_user or confirm call, continue the same job and perform the newly authorized action when the answer is sufficient. Do not stop with placeholder text like 'I'm ready to do it next.'",
15656
15801
  "If you ask the user a new question in this job, do not also close the loop in the same job.",
15657
15802
  "Write the smallest straightforward code for the current step. Avoid defensive fallback branches for hypothetical states that are not currently true.",
@@ -15839,12 +15984,13 @@ ${loopBlock}
15839
15984
  - Continue from the latest structured state. Treat WORKFLOW SNAPSHOT, EXECUTION CHECKPOINT, RECENT REFERENTS, SESSION HEAP, and AGENT LOOP STATE as the working memory for this request.
15840
15985
  - Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
15841
15986
  - Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
15842
- - If the user names a record that is not already in the heap, fetch it from the graph instead of saying it is not in context.
15843
- - Treat user-provided names as human references, not exact keys. If one strong partial match exists, use it. If several plausible matches exist, ask the user to choose.
15844
15987
  - Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
15988
+ - Treat user-provided names, numbers, and labels as human references, not exact keys. Resolve them with code: check recent referents/heap first, then query the graph with the broadest supported \`search\` or \`filter\`, then retry with a few normalized/fuzzy/prefix variants when the first pass is empty or ambiguous. Only say a record does not exist after a reasonable lookup across the relevant class.
15989
+ - If one strong match exists, use it. If several plausible matches remain, use \`loop.ask_user({ type: 'choice', ... })\` with the grounded candidates instead of guessing.
15845
15990
  - If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
15846
15991
  - For comparisons, rankings, selections, or summaries, first identify the rule you are using. If that rule is not clear from the user request and DOMAIN REFERENCE, ask the user before choosing anything.
15847
15992
  - When the ranking, comparison, or selection rule is unclear, the minimum next step is the clarification itself. Do not run a placeholder query for a provisional winner before asking.
15993
+ - If a user request matches both a domain type/effect and a loop helper, prioritize the domain type/effect. For example, if DOMAIN REFERENCE contains a \`Task\` class and the user asks to create a task, create the domain task record; do not call \`loop.create_task(...)\` unless you are only tracking your own workflow.
15848
15994
  - Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
15849
15995
  - If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
15850
15996
  - Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
@@ -15855,6 +16001,14 @@ ${loopBlock}
15855
16001
  - Use \`loop.open_decision(...)\` to persist grounded candidates, \`loop.close_decision(...)\` to resolve one, and \`loop.close_loop(...)\` when the workflow is completed, canceled, or blocked.
15856
16002
  - If you ask a new question in the current job, do not also close the loop in that same job.
15857
16003
 
16004
+ \u2500\u2500\u2500 LOOP HELPER REFERENCE \u2500\u2500\u2500
16005
+ - \`loop.ask_user(...)\`: pause the current job for missing input; use \`type: 'choice'\` only for a short grounded shortlist.
16006
+ - \`loop.confirm(...)\`: pause for yes/no approval before a consequential action, then branch on the returned boolean.
16007
+ - \`loop.open_decision(...)\`: save explicit candidates that later jobs can revisit; each candidate needs an \`id\`.
16008
+ - \`loop.close_decision(...)\`: resolve an open decision with a stored \`selectedId\` and optional rationale.
16009
+ - \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short resumable task list for the agent's workflow; these are not domain \`Task\` records.
16010
+ - \`loop.close_loop(...)\`: record the workflow outcome when it is completed, canceled, or blocked.
16011
+
15858
16012
  \u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
15859
16013
  - Import from \`./sandbox-tools\`.
15860
16014
  - If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
@@ -15864,6 +16018,7 @@ ${loopBlock}
15864
16018
  - Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
15865
16019
  - Use \`ClassName.count()\` for totals, \`ClassName.page({ page, perPage, saveAs })\` when you need \`items\` plus \`totalCount\` or \`hasMore\`, \`ClassName.list({ page, perPage, saveAs })\` for one page of records, and \`ClassName.iterate({ perPage, maxItems })\` for large scans.
15866
16020
  - \`perPage\` defaults to \`100\` and is capped at \`100\`.
16021
+ - A single \`list(...)\` or \`page(...)\` call never proves there are no more records. For "all", "every", exports, broad scans, or exhaustive searches, use \`iterate(...)\` when available or loop \`page(...)\` until \`hasMore\` is false.
15867
16022
  - Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
15868
16023
  - A property appearing on a record does not make it valid in \`filter\` or \`sort\`; only use fields and operators that are explicitly exposed in DOMAIN REFERENCE.
15869
16024
  - Choose \`sort.field\` verbatim from the sortable fields listed in DOMAIN REFERENCE. Do not sort by relationship names, related-record collections, counts, totals, or other derived metrics unless they are explicitly listed as sortable.