@granular-software/sdk 0.4.11 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var Automerge = require('@automerge/automerge');
4
+ var metamodelEffectBehaviors = require('@granular-software/metamodel-effect-behaviors');
5
+ var metamodelPresetDefault = require('@granular-software/metamodel-preset-default');
4
6
 
5
7
  function _interopNamespace(e) {
6
8
  if (e && e.__esModule) return e;
@@ -4650,7 +4652,7 @@ var Session = class {
4650
4652
  * Respond to a prompt request from the sandbox
4651
4653
  */
4652
4654
  async answerPrompt(promptId, answer) {
4653
- await this.client.call("prompt.answer", { promptId, value: answer });
4655
+ await this.client.call("prompt.answer", { promptId, answer, value: answer });
4654
4656
  }
4655
4657
  /**
4656
4658
  * Get the current list of available effects.
@@ -4967,13 +4969,31 @@ import { ${allImports} } from "./sandbox-tools";
4967
4969
  });
4968
4970
  }
4969
4971
  setupEventHandlers() {
4972
+ this.client.on("open", (payload) => this.emit("open", payload || {}));
4970
4973
  this.client.on("sync", (doc) => {
4971
4974
  this.currentDomainRevision = this.extractDomainRevisionFromDoc(doc);
4972
4975
  this.emit("sync", doc);
4973
4976
  this.checkForToolChanges();
4974
4977
  });
4975
- this.client.on("prompt", (prompt) => this.emit("prompt", prompt));
4978
+ const emitPrompt = (payload) => {
4979
+ const raw = payload;
4980
+ const prompt = raw.prompt || {
4981
+ id: typeof raw.id === "string" ? raw.id : String(raw.promptId || ""),
4982
+ type: raw.type === "confirm" || raw.type === "choice" || raw.type === "input" ? raw.type : raw.promptType === "confirm" || raw.promptType === "choice" ? raw.promptType : "input",
4983
+ title: typeof raw.title === "string" ? raw.title : "Input required",
4984
+ message: typeof raw.message === "string" ? raw.message : "",
4985
+ options: raw.options,
4986
+ defaultValue: raw.defaultValue,
4987
+ placeholder: raw.placeholder,
4988
+ allowEmpty: raw.allowEmpty,
4989
+ metadata: raw.metadata
4990
+ };
4991
+ this.emit("prompt", prompt);
4992
+ };
4993
+ this.client.on("prompt", emitPrompt);
4994
+ this.client.on("prompt.request", emitPrompt);
4976
4995
  this.client.on("disconnect", (payload) => this.emit("disconnect", payload || {}));
4996
+ this.client.on("reconnect_error", (payload) => this.emit("reconnect_error", payload || {}));
4977
4997
  this.client.on("job.status", (data) => {
4978
4998
  this.emit("job:status", data);
4979
4999
  });
@@ -5045,6 +5065,8 @@ function normalizeJobStatus(status) {
5045
5065
  case "failed":
5046
5066
  case "running":
5047
5067
  case "queued":
5068
+ case "awaitingTool":
5069
+ case "awaitingHuman":
5048
5070
  case "succeeded":
5049
5071
  case "timeout":
5050
5072
  case "canceled":
@@ -5179,6 +5201,19 @@ var JobImplementation = class {
5179
5201
  this.client.on(`job.${id}.error`, (error) => {
5180
5202
  this.finalize("failed", void 0, error);
5181
5203
  });
5204
+ this.client.on("job.status", (data) => {
5205
+ const jobData = data;
5206
+ if (jobData.jobId !== id) {
5207
+ return;
5208
+ }
5209
+ const normalizedStatus = normalizeJobStatus(jobData.status);
5210
+ this.status = normalizedStatus;
5211
+ this.metadata.status = normalizedStatus;
5212
+ if (normalizedStatus === "running") {
5213
+ this.markStarted();
5214
+ }
5215
+ this.emit("status", normalizedStatus);
5216
+ });
5182
5217
  this.client.on("job.completed", (data) => {
5183
5218
  const jobData = data;
5184
5219
  if (jobData.jobId === id) {
@@ -5372,6 +5407,123 @@ function resolveApiUrl(explicitApiUrl, mode) {
5372
5407
  }
5373
5408
  return resolveEndpointMode(mode) === "local" ? LOCAL_API_URL : PRODUCTION_API_URL;
5374
5409
  }
5410
+ function computeEffectKey(effect) {
5411
+ const attachedClass = effect.className?.trim();
5412
+ if (!attachedClass) {
5413
+ return `global:${effect.name}`;
5414
+ }
5415
+ return effect.static ? `class:${attachedClass}:static:${effect.name}` : `class:${attachedClass}:instance:${effect.name}`;
5416
+ }
5417
+ function normalizeEffectBehaviors(value) {
5418
+ return metamodelEffectBehaviors.normalizeEffectBehaviorSummary(
5419
+ value
5420
+ ) || {};
5421
+ }
5422
+ function resolveInvocationMode(context) {
5423
+ const mode = context?.invocation?.mode;
5424
+ if (mode === "dryRun" || mode === "reverse") {
5425
+ return mode;
5426
+ }
5427
+ return "execute";
5428
+ }
5429
+ function resolveReverseEffect(effectMap, currentEffect, request, behaviors) {
5430
+ const explicitReverseHandler = request.context?.invocation?.reverseHandler?.trim();
5431
+ const configuredReverseHandler = behaviors.reverse?.handler?.trim();
5432
+ const reverseHandler = explicitReverseHandler || configuredReverseHandler;
5433
+ if (!reverseHandler) {
5434
+ return void 0;
5435
+ }
5436
+ if (reverseHandler.includes(":")) {
5437
+ return effectMap.get(reverseHandler);
5438
+ }
5439
+ const directMatch = effectMap.get(reverseHandler);
5440
+ if (directMatch) {
5441
+ return directMatch;
5442
+ }
5443
+ const candidateKeys = [
5444
+ computeEffectKey({
5445
+ name: reverseHandler,
5446
+ className: currentEffect.className,
5447
+ static: currentEffect.static
5448
+ }),
5449
+ computeEffectKey({
5450
+ name: reverseHandler
5451
+ })
5452
+ ];
5453
+ for (const candidateKey of candidateKeys) {
5454
+ const candidate = effectMap.get(candidateKey);
5455
+ if (candidate) {
5456
+ return candidate;
5457
+ }
5458
+ }
5459
+ return void 0;
5460
+ }
5461
+ function resolveHandlerForMode(effectMap, effect, request) {
5462
+ const behaviors = normalizeEffectBehaviors(request.context?.behaviors || effect.metamodels || void 0);
5463
+ const mode = resolveInvocationMode(request.context);
5464
+ if (mode === "dryRun") {
5465
+ if (effect.dryRunHandler) {
5466
+ return { effect, mode, handler: effect.dryRunHandler };
5467
+ }
5468
+ if (behaviors.dryRun?.enabled) {
5469
+ return { effect, mode, handler: effect.handler };
5470
+ }
5471
+ throw new Error(`Dry run is not supported for ${request.effectKey}`);
5472
+ }
5473
+ if (mode === "reverse") {
5474
+ if (effect.reverseHandler) {
5475
+ return { effect, mode, handler: effect.reverseHandler };
5476
+ }
5477
+ const reverseEffect = resolveReverseEffect(effectMap, effect, request, behaviors);
5478
+ if (reverseEffect) {
5479
+ return {
5480
+ effect: reverseEffect,
5481
+ mode,
5482
+ handler: reverseEffect.reverseHandler || reverseEffect.handler
5483
+ };
5484
+ }
5485
+ throw new Error(`Reverse execution is not supported for ${request.effectKey}`);
5486
+ }
5487
+ return { effect, mode, handler: effect.handler };
5488
+ }
5489
+ async function invokeRegisteredEffect(effectMap, request) {
5490
+ const effect = effectMap.get(request.effectKey);
5491
+ if (!effect) {
5492
+ throw new Error(`Effect handler not found: ${request.effectKey}`);
5493
+ }
5494
+ const resolved = resolveHandlerForMode(effectMap, effect, request);
5495
+ const context = {
5496
+ ...request.context || {},
5497
+ behaviors: normalizeEffectBehaviors(request.context?.behaviors || effect.metamodels || void 0),
5498
+ invocation: {
5499
+ mode: resolved.mode,
5500
+ sourceEffectKey: request.effectKey,
5501
+ sourceEffectName: request.effectName,
5502
+ ...request.context?.invocation?.reverseHandler ? { reverseHandler: request.context.invocation.reverseHandler } : {}
5503
+ }
5504
+ };
5505
+ if (resolved.effect.className && !resolved.effect.static && request.input && typeof request.input === "object" && "_objectId" in request.input) {
5506
+ const { _objectId, ...rest } = request.input;
5507
+ return resolved.handler(_objectId, rest, context);
5508
+ }
5509
+ return resolved.handler(request.input, context);
5510
+ }
5511
+ function buildFieldMetamodelMutations(fieldPath, spec) {
5512
+ return metamodelPresetDefault.DEFAULT_METAMODEL_PACKAGES.flatMap(
5513
+ (pkg) => pkg.manifest?.buildFieldMutations?.(fieldPath, spec) || []
5514
+ );
5515
+ }
5516
+ function buildModelMetamodelMutations(modelPath, spec) {
5517
+ return metamodelPresetDefault.DEFAULT_METAMODEL_PACKAGES.flatMap(
5518
+ (pkg) => pkg.manifest?.buildModelMutations?.(modelPath, spec) || []
5519
+ );
5520
+ }
5521
+ function buildEffectMetamodelMutations(toolPath, spec) {
5522
+ if (!spec) return [];
5523
+ return metamodelPresetDefault.DEFAULT_METAMODEL_PACKAGES.flatMap(
5524
+ (pkg) => pkg.manifest?.buildEffectMutations?.(toolPath, spec) || []
5525
+ );
5526
+ }
5375
5527
 
5376
5528
  // src/client.ts
5377
5529
  var STANDARD_MODULES_OPERATIONS = [
@@ -5387,7 +5539,7 @@ var STANDARD_MODULES_OPERATIONS = [
5387
5539
  var BUILTIN_MODULES = {
5388
5540
  "standard_modules": STANDARD_MODULES_OPERATIONS
5389
5541
  };
5390
- function computeEffectKey(effect) {
5542
+ function computeEffectKey2(effect) {
5391
5543
  const attachedClass = effect.className?.trim();
5392
5544
  if (!attachedClass) {
5393
5545
  return `global:${effect.name}`;
@@ -5964,6 +6116,87 @@ var Environment = class extends Session {
5964
6116
  }
5965
6117
  return ref;
5966
6118
  }
6119
+ async _runGraphql(query, label) {
6120
+ const result = await this.graphql(query);
6121
+ if (result.errors?.length) {
6122
+ throw new Error(`${label}: ${result.errors[0].message}`);
6123
+ }
6124
+ return result.data;
6125
+ }
6126
+ async _applyFieldMetamodels(fieldPath, spec) {
6127
+ for (const mutation of buildFieldMetamodelMutations(fieldPath, spec)) {
6128
+ await this._runGraphql(mutation.query, mutation.label);
6129
+ }
6130
+ }
6131
+ async _applyModelMetamodels(modelPath, op) {
6132
+ for (const mutation of buildModelMetamodelMutations(modelPath, op)) {
6133
+ await this._runGraphql(mutation.query, mutation.label);
6134
+ }
6135
+ }
6136
+ async _ensureWorkspaceToolsRoot() {
6137
+ await this._runGraphql(
6138
+ `mutation { create_model(path: "workspace", label: "workspace") { model { path } } }`,
6139
+ "ensure workspace"
6140
+ ).catch((error) => {
6141
+ if (!error.message.includes("already exists")) throw error;
6142
+ });
6143
+ await this._runGraphql(
6144
+ `mutation { at(path: "workspace") { create_submodel(subpath: "tools", label: "Tools") { model { path } } } }`,
6145
+ "ensure workspace:tools"
6146
+ ).catch((error) => {
6147
+ if (!error.message.includes("already exists")) throw error;
6148
+ });
6149
+ await this._runGraphql(
6150
+ `mutation { at(path: "workspace:tools") { create_submodel(subpath: "declared", label: "Declared") { model { path } } } }`,
6151
+ "ensure workspace:tools:declared"
6152
+ ).catch((error) => {
6153
+ if (!error.message.includes("already exists")) throw error;
6154
+ });
6155
+ }
6156
+ async _storeEffectSchemas(toolPath, effect) {
6157
+ await this._runGraphql(
6158
+ `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "inputSchema", label: "inputSchema") { set_string_value(value: ${JSON.stringify(JSON.stringify(effect.inputSchema))}) { done } } } }`,
6159
+ `store inputSchema on ${toolPath}`
6160
+ ).catch((error) => {
6161
+ if (!error.message.includes("already exists")) throw error;
6162
+ });
6163
+ if (effect.outputSchema) {
6164
+ await this._runGraphql(
6165
+ `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "outputSchema", label: "outputSchema") { set_string_value(value: ${JSON.stringify(JSON.stringify(effect.outputSchema))}) { done } } } }`,
6166
+ `store outputSchema on ${toolPath}`
6167
+ ).catch((error) => {
6168
+ if (!error.message.includes("already exists")) throw error;
6169
+ });
6170
+ }
6171
+ }
6172
+ async _applyEffectMetamodels(toolPath, metamodels) {
6173
+ for (const mutation of buildEffectMetamodelMutations(toolPath, metamodels)) {
6174
+ await this._runGraphql(mutation.query, mutation.label);
6175
+ }
6176
+ }
6177
+ async _applyEffectDeclaration(effect, aliasMap) {
6178
+ let containerPath = "workspace:tools:declared";
6179
+ if (effect.attachedClass) {
6180
+ containerPath = this._resolveAlias(effect.attachedClass, aliasMap);
6181
+ } else {
6182
+ await this._ensureWorkspaceToolsRoot();
6183
+ }
6184
+ await this._runGraphql(
6185
+ `mutation { at(path: ${JSON.stringify(containerPath)}) { create_submodel(subpath: ${JSON.stringify(effect.name)}, label: ${JSON.stringify(effect.name)}) { model { path } } } }`,
6186
+ `create effect model ${containerPath}:${effect.name}`
6187
+ ).catch((error) => {
6188
+ if (!error.message.includes("already exists")) throw error;
6189
+ });
6190
+ const toolPath = `${containerPath}:${effect.name}`;
6191
+ if (effect.description) {
6192
+ await this._runGraphql(
6193
+ `mutation { at(path: ${JSON.stringify(toolPath)}) { set_description(description: ${JSON.stringify(effect.description)}) { done } } }`,
6194
+ `set effect description on ${toolPath}`
6195
+ );
6196
+ }
6197
+ await this._storeEffectSchemas(toolPath, effect);
6198
+ await this._applyEffectMetamodels(toolPath, effect.metamodels);
6199
+ }
5967
6200
  /**
5968
6201
  * Apply a single manifest operation via GraphQL
5969
6202
  */
@@ -5986,24 +6219,32 @@ var Environment = class extends Session {
5986
6219
  const extendsRef = op.extends ? this._resolveAlias(op.extends, aliasMap) : void 0;
5987
6220
  const instanceOfRef = op.instanceOf ? this._resolveAlias(op.instanceOf, aliasMap) : void 0;
5988
6221
  if (extendsRef) {
5989
- await this.graphql(
5990
- `mutation { create_model(path: "${op.create}", label: "${label}") { model { path } } }`
6222
+ await this._runGraphql(
6223
+ `mutation { create_model(path: "${op.create}", label: "${label}") { model { path } } }`,
6224
+ `create model ${op.create}`
5991
6225
  );
5992
- await this.graphql(
5993
- `mutation { at(path: "${op.create}") { add_superclass(superclass: "${extendsRef}") { model { path } } } }`
6226
+ await this._runGraphql(
6227
+ `mutation { at(path: "${op.create}") { add_superclass(superclass: "${extendsRef}") { model { path } } } }`,
6228
+ `add superclass ${extendsRef} to ${op.create}`
5994
6229
  );
5995
6230
  } else if (instanceOfRef) {
5996
- await this.graphql(
5997
- `mutation { at(path: "${instanceOfRef}") { instantiate(path: "${op.create}", label: "${label}") { model { path } } } }`
6231
+ await this._runGraphql(
6232
+ `mutation { at(path: "${instanceOfRef}") { instantiate(path: "${op.create}", label: "${label}") { model { path } } } }`,
6233
+ `instantiate ${op.create} from ${instanceOfRef}`
5998
6234
  );
5999
6235
  } else {
6000
- await this.graphql(
6001
- `mutation { create_model(path: "${op.create}", label: "${label}") { model { path } } }`
6236
+ await this._runGraphql(
6237
+ `mutation { create_model(path: "${op.create}", label: "${label}") { model { path } } }`,
6238
+ `create model ${op.create}`
6002
6239
  );
6003
6240
  }
6004
6241
  if (op.has) {
6005
6242
  await this._applyFields(op.create, op.has);
6006
6243
  }
6244
+ await this._applyModelMetamodels(op.create, op);
6245
+ if (op.withEffect) {
6246
+ await this._applyEffectDeclaration(op.withEffect, aliasMap);
6247
+ }
6007
6248
  return;
6008
6249
  }
6009
6250
  if (op.on) {
@@ -6011,8 +6252,15 @@ var Environment = class extends Session {
6011
6252
  if (op.has) {
6012
6253
  await this._applyFields(target, op.has);
6013
6254
  }
6255
+ await this._applyModelMetamodels(target, op);
6256
+ if (op.withEffect) {
6257
+ await this._applyEffectDeclaration(op.withEffect, aliasMap);
6258
+ }
6014
6259
  return;
6015
6260
  }
6261
+ if (op.withEffect) {
6262
+ await this._applyEffectDeclaration(op.withEffect, aliasMap);
6263
+ }
6016
6264
  }
6017
6265
  /**
6018
6266
  * Apply field definitions (has) to a model via GraphQL
@@ -6020,81 +6268,91 @@ var Environment = class extends Session {
6020
6268
  async _applyFields(modelPath, fields) {
6021
6269
  for (const [fieldName, spec] of Object.entries(fields)) {
6022
6270
  const fieldLabel = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
6023
- await this.graphql(
6271
+ await this._runGraphql(
6024
6272
  `mutation {
6025
- at(path: "${modelPath}") {
6026
- create_submodel(subpath: "${fieldName}", label: "${fieldLabel}") {
6273
+ at(path: ${JSON.stringify(modelPath)}) {
6274
+ create_submodel(subpath: ${JSON.stringify(fieldName)}, label: ${JSON.stringify(fieldLabel)}) {
6027
6275
  model { path }
6028
6276
  }
6029
6277
  }
6030
- }`
6031
- );
6278
+ }`,
6279
+ `create field ${modelPath}.${fieldName}`
6280
+ ).catch((error) => {
6281
+ if (!error.message.includes("already exists")) throw error;
6282
+ });
6032
6283
  if (spec.type) {
6033
- await this.graphql(
6284
+ await this._runGraphql(
6034
6285
  `mutation {
6035
- at(path: "${modelPath}") {
6036
- at(submodel: "${fieldName}") {
6037
- add_prototype(prototype: "${spec.type}") { done }
6286
+ at(path: ${JSON.stringify(modelPath)}) {
6287
+ at(submodel: ${JSON.stringify(fieldName)}) {
6288
+ add_prototype(prototype: ${JSON.stringify(spec.type)}) { done }
6038
6289
  }
6039
6290
  }
6040
- }`
6291
+ }`,
6292
+ `set prototype on ${modelPath}:${fieldName}`
6041
6293
  );
6042
6294
  }
6043
6295
  if (spec.description) {
6044
- await this.graphql(
6296
+ await this._runGraphql(
6045
6297
  `mutation {
6046
- at(path: "${modelPath}") {
6047
- at(submodel: "${fieldName}") {
6048
- set_description(description: "${spec.description}") { done }
6298
+ at(path: ${JSON.stringify(modelPath)}) {
6299
+ at(submodel: ${JSON.stringify(fieldName)}) {
6300
+ set_description(description: ${JSON.stringify(spec.description)}) { done }
6049
6301
  }
6050
6302
  }
6051
- }`
6303
+ }`,
6304
+ `set description on ${modelPath}:${fieldName}`
6052
6305
  );
6053
6306
  }
6054
6307
  if (spec.value !== void 0 && spec.value !== null) {
6055
6308
  if (typeof spec.value === "string") {
6056
- await this.graphql(
6309
+ await this._runGraphql(
6057
6310
  `mutation {
6058
- at(path: "${modelPath}") {
6059
- at(submodel: "${fieldName}") {
6060
- set_string_value(value: "${spec.value}") { done }
6311
+ at(path: ${JSON.stringify(modelPath)}) {
6312
+ at(submodel: ${JSON.stringify(fieldName)}) {
6313
+ set_string_value(value: ${JSON.stringify(spec.value)}) { done }
6061
6314
  }
6062
6315
  }
6063
- }`
6316
+ }`,
6317
+ `set string value on ${modelPath}:${fieldName}`
6064
6318
  );
6065
6319
  } else if (typeof spec.value === "number") {
6066
- await this.graphql(
6320
+ await this._runGraphql(
6067
6321
  `mutation {
6068
- at(path: "${modelPath}") {
6069
- at(submodel: "${fieldName}") {
6322
+ at(path: ${JSON.stringify(modelPath)}) {
6323
+ at(submodel: ${JSON.stringify(fieldName)}) {
6070
6324
  set_number_value(value: ${spec.value}) { done }
6071
6325
  }
6072
6326
  }
6073
- }`
6327
+ }`,
6328
+ `set number value on ${modelPath}:${fieldName}`
6074
6329
  );
6075
6330
  } else if (typeof spec.value === "boolean") {
6076
- await this.graphql(
6331
+ await this._runGraphql(
6077
6332
  `mutation {
6078
- at(path: "${modelPath}") {
6079
- at(submodel: "${fieldName}") {
6333
+ at(path: ${JSON.stringify(modelPath)}) {
6334
+ at(submodel: ${JSON.stringify(fieldName)}) {
6080
6335
  set_boolean_value(value: ${spec.value}) { done }
6081
6336
  }
6082
6337
  }
6083
- }`
6338
+ }`,
6339
+ `set boolean value on ${modelPath}:${fieldName}`
6084
6340
  );
6085
6341
  }
6086
6342
  }
6087
6343
  if (spec.ref) {
6088
- await this.graphql(
6344
+ await this._runGraphql(
6089
6345
  `mutation {
6090
- at(path: "${modelPath}") {
6091
- at(submodel: "${fieldName}") {
6092
- set_reference(reference: "${spec.ref}") { done }
6346
+ at(path: ${JSON.stringify(modelPath)}) {
6347
+ at(submodel: ${JSON.stringify(fieldName)}) {
6348
+ set_reference(reference: ${JSON.stringify(spec.ref)}) { done }
6093
6349
  }
6094
6350
  }
6095
- }`
6351
+ }`,
6352
+ `set reference on ${modelPath}:${fieldName}`
6096
6353
  );
6097
6354
  }
6355
+ await this._applyFieldMetamodels(`${modelPath}:${fieldName}`, spec);
6098
6356
  if (spec.has) {
6099
6357
  await this._applyFields(`${modelPath}:${fieldName}`, spec.has);
6100
6358
  }
@@ -6434,7 +6692,7 @@ var Granular = class {
6434
6692
  }
6435
6693
  serializeEffect(effect) {
6436
6694
  return {
6437
- effectKey: computeEffectKey(effect),
6695
+ effectKey: computeEffectKey2(effect),
6438
6696
  name: effect.name,
6439
6697
  description: effect.description,
6440
6698
  inputSchema: effect.inputSchema,
@@ -6470,23 +6728,54 @@ var Granular = class {
6470
6728
  const host = await this.ensureSandboxEffectHost(sandboxId);
6471
6729
  await this.publishSandboxEffectCatalog(host);
6472
6730
  }
6473
- startEffectHostHeartbeat(host) {
6474
- if (host.heartbeatTimer) {
6475
- clearInterval(host.heartbeatTimer);
6731
+ recoverEffectHost(host, error) {
6732
+ if (host.recovering) {
6733
+ return;
6734
+ }
6735
+ if (this.sandboxEffectHosts.get(host.sandboxId) !== host) {
6736
+ return;
6476
6737
  }
6477
- host.wsClient.call("client.heartbeat", {}).catch((error) => {
6738
+ host.recovering = true;
6739
+ this.stopEffectHostHeartbeat(host);
6740
+ this.sandboxEffectHosts.delete(host.sandboxId);
6741
+ try {
6742
+ host.wsClient.disconnect();
6743
+ } catch (disconnectError) {
6478
6744
  console.warn(
6479
- `[Granular] Initial effect host heartbeat failed for sandbox ${host.sandboxId}:`,
6480
- error
6745
+ `[Granular] Failed to disconnect stale effect host for sandbox ${host.sandboxId}:`,
6746
+ disconnectError
6747
+ );
6748
+ }
6749
+ void this.ensureSandboxEffectHost(host.sandboxId).catch((reconnectError) => {
6750
+ console.error(
6751
+ `[Granular] Failed to recover effect host for sandbox ${host.sandboxId} after heartbeat failure:`,
6752
+ reconnectError
6481
6753
  );
6754
+ console.error("[Granular] Original heartbeat failure:", error);
6482
6755
  });
6483
- host.heartbeatTimer = setInterval(() => {
6756
+ }
6757
+ startEffectHostHeartbeat(host) {
6758
+ if (host.heartbeatTimer) {
6759
+ clearInterval(host.heartbeatTimer);
6760
+ }
6761
+ host.heartbeatInFlight = false;
6762
+ const sendHeartbeat = (failureLabel, recoverOnFailure) => {
6763
+ if (host.heartbeatInFlight || host.recovering) {
6764
+ return;
6765
+ }
6766
+ host.heartbeatInFlight = true;
6484
6767
  host.wsClient.call("client.heartbeat", {}).catch((error) => {
6485
- console.warn(
6486
- `[Granular] Effect host heartbeat failed for sandbox ${host.sandboxId}:`,
6487
- error
6488
- );
6768
+ console.warn(`${failureLabel} ${host.sandboxId}:`, error);
6769
+ if (recoverOnFailure) {
6770
+ this.recoverEffectHost(host, error);
6771
+ }
6772
+ }).finally(() => {
6773
+ host.heartbeatInFlight = false;
6489
6774
  });
6775
+ };
6776
+ sendHeartbeat("[Granular] Initial effect host heartbeat failed for sandbox", false);
6777
+ host.heartbeatTimer = setInterval(() => {
6778
+ sendHeartbeat("[Granular] Effect host heartbeat failed for sandbox", true);
6490
6779
  }, 1e4);
6491
6780
  }
6492
6781
  stopEffectHostHeartbeat(host) {
@@ -6495,6 +6784,7 @@ var Granular = class {
6495
6784
  }
6496
6785
  clearInterval(host.heartbeatTimer);
6497
6786
  host.heartbeatTimer = null;
6787
+ host.heartbeatInFlight = false;
6498
6788
  }
6499
6789
  async synchronizeEffectHost(host) {
6500
6790
  await host.wsClient.call("client.hello", {
@@ -6530,19 +6820,13 @@ var Granular = class {
6530
6820
  effectClientId,
6531
6821
  clientId,
6532
6822
  wsClient,
6533
- heartbeatTimer: null
6823
+ heartbeatTimer: null,
6824
+ heartbeatInFlight: false,
6825
+ recovering: false
6534
6826
  };
6535
6827
  wsClient.registerRpcHandler("effect.invoke", async (params) => {
6536
6828
  const request = params;
6537
- const effect = this.getSandboxEffectMap(sandboxId).get(request.effectKey);
6538
- if (!effect) {
6539
- throw new Error(`Effect handler not found: ${request.effectKey}`);
6540
- }
6541
- if (effect.className && !effect.static && request.input && typeof request.input === "object" && "_objectId" in request.input) {
6542
- const { _objectId, ...rest } = request.input;
6543
- return effect.handler(_objectId, rest, request.context);
6544
- }
6545
- return effect.handler(request.input, request.context);
6829
+ return invokeRegisteredEffect(this.getSandboxEffectMap(sandboxId), request);
6546
6830
  });
6547
6831
  wsClient.on("open", () => {
6548
6832
  void this.synchronizeEffectHost(host).catch((error) => {
@@ -6585,7 +6869,7 @@ var Granular = class {
6585
6869
  async registerEffect(sandboxNameOrId, effect) {
6586
6870
  const sandbox = await this.findOrCreateSandbox(sandboxNameOrId);
6587
6871
  const sandboxId = sandbox.sandboxId;
6588
- this.getSandboxEffectMap(sandboxId).set(computeEffectKey(effect), effect);
6872
+ this.getSandboxEffectMap(sandboxId).set(computeEffectKey2(effect), effect);
6589
6873
  await this.syncSandboxEffectCatalog(sandboxId);
6590
6874
  }
6591
6875
  /**
@@ -6598,7 +6882,7 @@ var Granular = class {
6598
6882
  const sandboxId = sandbox.sandboxId;
6599
6883
  const map = this.getSandboxEffectMap(sandboxId);
6600
6884
  for (const effect of effects) {
6601
- map.set(computeEffectKey(effect), effect);
6885
+ map.set(computeEffectKey2(effect), effect);
6602
6886
  }
6603
6887
  await this.syncSandboxEffectCatalog(sandboxId);
6604
6888
  }
@@ -6879,5 +7163,7 @@ exports.Environment = Environment;
6879
7163
  exports.Granular = Granular;
6880
7164
  exports.Session = Session;
6881
7165
  exports.WSClient = WSClient;
7166
+ exports.invokeRegisteredEffect = invokeRegisteredEffect;
7167
+ exports.normalizeEffectBehaviors = normalizeEffectBehaviors;
6882
7168
  //# sourceMappingURL=index.js.map
6883
7169
  //# sourceMappingURL=index.js.map