@axiom-lattice/core 6.3.3 → 6.4.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.
package/dist/index.js CHANGED
@@ -1621,6 +1621,7 @@ __export(index_exports, {
1621
1621
  CapabilityBundleValidationError: () => CapabilityBundleValidationError,
1622
1622
  CapabilityToolNameConflictError: () => CapabilityToolNameConflictError,
1623
1623
  CapabilityToolRegistry: () => CapabilityToolRegistry,
1624
+ ChannelConnectionLeader: () => ChannelConnectionLeader,
1624
1625
  ChunkBuffer: () => ChunkBuffer,
1625
1626
  ChunkBufferLatticeManager: () => ChunkBufferLatticeManager,
1626
1627
  CollectionLatticeManager: () => CollectionLatticeManager,
@@ -1632,7 +1633,9 @@ __export(index_exports, {
1632
1633
  DEFAULT_CALIBRATION_PROBES: () => DEFAULT_CALIBRATION_PROBES,
1633
1634
  DEFAULT_CONNECT_TIMEOUT_MS: () => DEFAULT_CONNECT_TIMEOUT_MS,
1634
1635
  DEFAULT_DELAY_MS: () => DEFAULT_DELAY_MS,
1636
+ DEFAULT_HANDLER_TIMEOUT_MS: () => DEFAULT_HANDLER_TIMEOUT_MS,
1635
1637
  DEFAULT_MAX_ATTEMPTS: () => DEFAULT_MAX_ATTEMPTS,
1638
+ DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS: () => DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS,
1636
1639
  DaytonaInstance: () => DaytonaInstance,
1637
1640
  DaytonaProvider: () => DaytonaProvider,
1638
1641
  DefaultScheduleClient: () => DefaultScheduleClient,
@@ -1650,6 +1653,7 @@ __export(index_exports, {
1650
1653
  HEALTH_CHECK_INTERVAL_MS: () => HEALTH_CHECK_INTERVAL_MS,
1651
1654
  HEALTH_CHECK_TIMEOUT_MS: () => HEALTH_CHECK_TIMEOUT_MS,
1652
1655
  HumanMessage: () => import_messages9.HumanMessage,
1656
+ INLINE_INPUT_LIMIT_BYTES: () => INLINE_INPUT_LIMIT_BYTES2,
1653
1657
  IdRemapper: () => IdRemapper,
1654
1658
  InMemoryA2AApiKeyStore: () => InMemoryA2AApiKeyStore,
1655
1659
  InMemoryAgentWebAppStore: () => InMemoryAgentWebAppStore,
@@ -1690,10 +1694,12 @@ __export(index_exports, {
1690
1694
  LocalSandboxProvider: () => LocalSandboxProvider,
1691
1695
  LoggerLatticeManager: () => LoggerLatticeManager,
1692
1696
  MAX_BACKOFF_MS: () => MAX_BACKOFF_MS,
1697
+ MAX_HANDLER_TIMEOUT_MS: () => MAX_HANDLER_TIMEOUT_MS,
1693
1698
  MAX_LINE_LENGTH: () => MAX_LINE_LENGTH,
1694
1699
  MAX_PACK_ENTRIES: () => MAX_PACK_ENTRIES,
1695
1700
  MAX_PACK_FILE_BYTES: () => MAX_PACK_FILE_BYTES,
1696
1701
  MAX_PACK_TOTAL_BYTES: () => MAX_PACK_TOTAL_BYTES,
1702
+ MIN_HANDLER_TIMEOUT_MS: () => MIN_HANDLER_TIMEOUT_MS,
1697
1703
  ManagedTaskStartError: () => ManagedTaskStartError,
1698
1704
  McpLatticeManager: () => McpLatticeManager,
1699
1705
  MemoryBackend: () => MemoryBackend,
@@ -1712,8 +1718,12 @@ __export(index_exports, {
1712
1718
  PACK_DIRECTORY_TIMEOUT_SECONDS: () => PACK_DIRECTORY_TIMEOUT_SECONDS,
1713
1719
  PLATFORM_SECRET_ENV_VARS: () => PLATFORM_SECRET_ENV_VARS,
1714
1720
  PLUGIN_CATALOG_CHANGED_TOPIC: () => PLUGIN_CATALOG_CHANGED_TOPIC,
1721
+ PLUGIN_HOST_DIR: () => PLUGIN_HOST_DIR,
1722
+ PLUGIN_HOST_SERVER_SOURCE: () => PLUGIN_HOST_SERVER_SOURCE,
1723
+ PLUGIN_HOST_VERSION: () => PLUGIN_HOST_VERSION,
1715
1724
  PersonalAssistantConfig: () => PersonalAssistantConfig,
1716
1725
  PinoLoggerClient: () => PinoLoggerClient,
1726
+ PluginDeploymentRuntimeRegistry: () => PluginDeploymentRuntimeRegistry,
1717
1727
  PluginRegistry: () => PluginRegistry,
1718
1728
  PostgresDatabase: () => PostgresDatabase,
1719
1729
  ProjectBotMembershipIdConflictError: () => ProjectBotMembershipIdConflictError,
@@ -1775,7 +1785,9 @@ __export(index_exports, {
1775
1785
  appendUiFence: () => appendUiFence,
1776
1786
  assertSafeUpstreamUrl: () => assertSafeUpstreamUrl,
1777
1787
  buildBundleFromDist: () => buildBundleFromDist,
1788
+ buildConnectionPayload: () => buildConnectionPayload2,
1778
1789
  buildGrepResultsDict: () => buildGrepResultsDict,
1790
+ buildHandlerCommand: () => buildHandlerCommand,
1779
1791
  buildInput: () => buildInput,
1780
1792
  buildNamedVolumeName: () => buildNamedVolumeName,
1781
1793
  buildRemotePackDirectoryCommand: () => buildRemotePackDirectoryCommand,
@@ -1843,9 +1855,11 @@ __export(index_exports, {
1843
1855
  decrypt: () => decrypt,
1844
1856
  decryptConfigFields: () => decryptConfigFields,
1845
1857
  describeCronExpression: () => describeCronExpression,
1858
+ disconnectAllChannels: () => disconnectAllChannels,
1846
1859
  documentLearningPlugin: () => documentLearningPlugin,
1847
1860
  documentParserPlugin: () => documentParserPlugin,
1848
1861
  embeddingsLatticeManager: () => embeddingsLatticeManager,
1862
+ encodeInput: () => encodeInput,
1849
1863
  encrypt: () => encrypt,
1850
1864
  encryptConfigFields: () => encryptConfigFields,
1851
1865
  ensureBuiltinAgentsForTenant: () => ensureBuiltinAgentsForTenant,
@@ -1889,6 +1903,7 @@ __export(index_exports, {
1889
1903
  getModelLattice: () => getModelLattice,
1890
1904
  getNextCronTime: () => getNextCronTime,
1891
1905
  getOrCreateCollectionVectorStore: () => getOrCreateCollectionVectorStore,
1906
+ getPluginDeploymentService: () => getPluginDeploymentService,
1892
1907
  getPluginSkillManifestVersion: () => getPluginSkillManifestVersion,
1893
1908
  getQueueLattice: () => getQueueLattice,
1894
1909
  getReplicaId: () => getReplicaId,
@@ -1917,6 +1932,8 @@ __export(index_exports, {
1917
1932
  hasChunkBuffer: () => hasChunkBuffer,
1918
1933
  hasDelegatedWorkerToken: () => hasDelegatedWorkerToken,
1919
1934
  hasRuntimeCapabilityPolicy: () => hasRuntimeCapabilityPolicy,
1935
+ installPluginHost: () => installPluginHost,
1936
+ invokeResident: () => invokeResident,
1920
1937
  invokeWithRetry: () => invokeWithRetry,
1921
1938
  isBuiltInSkill: () => isBuiltInSkill,
1922
1939
  isDirectRoom: () => isDirectRoom,
@@ -1944,6 +1961,8 @@ __export(index_exports, {
1944
1961
  parallelLimit: () => parallelLimit,
1945
1962
  parseCronExpression: () => parseCronExpression,
1946
1963
  parseDirectRoomType: () => parseDirectRoomType,
1964
+ parseHandlerEnvelope: () => parseHandlerEnvelope,
1965
+ parseHostArgs: () => parseHostArgs,
1947
1966
  parseJudgeVerdict: () => parseJudgeVerdict,
1948
1967
  parsePackSize: () => parsePackSize,
1949
1968
  parseQueuedExecutionMode: () => import_protocols2.parseQueuedExecutionMode,
@@ -1952,6 +1971,7 @@ __export(index_exports, {
1952
1971
  parseTrustedRunContext: () => import_protocols2.parseTrustedRunContext,
1953
1972
  parseYaml: () => parseYaml,
1954
1973
  performStringReplacement: () => performStringReplacement,
1974
+ pluginHostContentHash: () => pluginHostContentHash,
1955
1975
  postProcessTaskMutationResponse: () => postProcessTaskMutationResponse,
1956
1976
  prepareTaskDispatch: () => prepareTaskDispatch,
1957
1977
  projectMutationMutex: () => projectMutationMutex,
@@ -1990,6 +2010,7 @@ __export(index_exports, {
1990
2010
  renderTemplate: () => renderTemplate,
1991
2011
  resetReplicaIdForTests: () => resetReplicaIdForTests,
1992
2012
  resolveEnabledPluginSkill: () => resolveEnabledPluginSkill,
2013
+ resolveHandlerTimeoutMs: () => resolveHandlerTimeoutMs,
1993
2014
  resolveJudgeModelKey: () => resolveJudgeModelKey,
1994
2015
  resolvePath: () => resolvePath,
1995
2016
  resolvePluginConnections: () => resolvePluginConnections,
@@ -2009,6 +2030,7 @@ __export(index_exports, {
2009
2030
  setBindingRegistry: () => setBindingRegistry,
2010
2031
  setEvalRunService: () => setEvalRunService,
2011
2032
  setMenuRegistry: () => setMenuRegistry,
2033
+ setPluginDeploymentService: () => setPluginDeploymentService,
2012
2034
  setSandboxPluginCatalog: () => setSandboxPluginCatalog,
2013
2035
  skillLatticeManager: () => skillLatticeManager,
2014
2036
  sqlDatabaseManager: () => sqlDatabaseManager,
@@ -3706,6 +3728,9 @@ var InMemoryProjectStore = class {
3706
3728
  workspaceId,
3707
3729
  name: data.name,
3708
3730
  description: data.description,
3731
+ // Persist `kind` like the Local/PostgreSQL stores so `public`/`personal`/
3732
+ // `training` project semantics survive in in-memory deployments.
3733
+ ...data.kind === void 0 ? {} : { kind: data.kind },
3709
3734
  config: data.config === void 0 && Object.keys(capabilityBundleIds).length === 0 ? void 0 : { ...data.config ?? {}, ...capabilityBundleIds },
3710
3735
  createdAt: existing?.createdAt ?? now,
3711
3736
  updatedAt: now
@@ -5325,6 +5350,93 @@ var InMemoryOutboxStore = class {
5325
5350
  }
5326
5351
  };
5327
5352
 
5353
+ // src/store_lattice/InMemoryPluginDeploymentStore.ts
5354
+ var InMemoryPluginDeploymentStore = class {
5355
+ constructor() {
5356
+ this.entries = /* @__PURE__ */ new Map();
5357
+ }
5358
+ /**
5359
+ * Lists every deployment for a tenant.
5360
+ *
5361
+ * @param tenantId - Tenant whose deployments are listed
5362
+ * @returns Defensive copies of the tenant's deployments
5363
+ */
5364
+ async listByTenant(tenantId2) {
5365
+ return [...this.entries.values()].filter((entry) => entry.tenantId === tenantId2).map((entry) => structuredClone(entry));
5366
+ }
5367
+ /**
5368
+ * Returns the deployment for a tenant + plugin type.
5369
+ *
5370
+ * @param tenantId - Owning tenant
5371
+ * @param pluginType - Plugin type key
5372
+ * @returns Defensive copy of the deployment, or undefined when absent
5373
+ */
5374
+ async get(tenantId2, pluginType) {
5375
+ const entry = this.entries.get(entryKey2(tenantId2, pluginType));
5376
+ return entry ? structuredClone(entry) : void 0;
5377
+ }
5378
+ /**
5379
+ * Inserts or replaces a deployment keyed by tenant + plugin type.
5380
+ *
5381
+ * On conflict the existing `id` and `createdAt` are preserved while every
5382
+ * other field (including `updatedAt`, when supplied) is updated — mirroring
5383
+ * the PostgreSQL store's `ON CONFLICT` semantics.
5384
+ *
5385
+ * @param deployment - Deployment record to persist
5386
+ * @returns Defensive copy of the stored deployment
5387
+ */
5388
+ async upsert(deployment) {
5389
+ const key5 = entryKey2(deployment.tenantId, deployment.pluginType);
5390
+ const existing = this.entries.get(key5);
5391
+ const next = {
5392
+ ...structuredClone(deployment),
5393
+ id: existing?.id ?? deployment.id,
5394
+ createdAt: existing?.createdAt ?? deployment.createdAt,
5395
+ updatedAt: deployment.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5396
+ };
5397
+ this.entries.set(key5, next);
5398
+ return structuredClone(next);
5399
+ }
5400
+ /**
5401
+ * Applies a partial update to a deployment.
5402
+ *
5403
+ * `patch.createdAt` is ignored (immutable) and `updatedAt` is taken from the
5404
+ * patch when supplied, otherwise set to the current time — matching the
5405
+ * PostgreSQL store.
5406
+ *
5407
+ * @param tenantId - Owning tenant
5408
+ * @param pluginType - Plugin type key
5409
+ * @param patch - Mutable fields to apply
5410
+ * @returns Defensive copy of the updated deployment, or undefined when absent
5411
+ */
5412
+ async updateStatus(tenantId2, pluginType, patch) {
5413
+ const key5 = entryKey2(tenantId2, pluginType);
5414
+ const existing = this.entries.get(key5);
5415
+ if (!existing) return void 0;
5416
+ const next = {
5417
+ ...existing,
5418
+ ...patch,
5419
+ createdAt: existing.createdAt,
5420
+ updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
5421
+ };
5422
+ this.entries.set(key5, next);
5423
+ return structuredClone(next);
5424
+ }
5425
+ /**
5426
+ * Deletes a deployment by tenant + plugin type.
5427
+ *
5428
+ * @param tenantId - Owning tenant
5429
+ * @param pluginType - Plugin type key
5430
+ * @returns Whether a record was removed
5431
+ */
5432
+ async delete(tenantId2, pluginType) {
5433
+ return this.entries.delete(entryKey2(tenantId2, pluginType));
5434
+ }
5435
+ };
5436
+ function entryKey2(tenantId2, pluginType) {
5437
+ return JSON.stringify([tenantId2, pluginType]);
5438
+ }
5439
+
5328
5440
  // src/store_lattice/InMemoryUserStore.ts
5329
5441
  var InMemoryUserStore = class {
5330
5442
  constructor() {
@@ -6987,6 +7099,7 @@ var defaultProjectRoomReadStateStore = new InMemoryProjectRoomReadStateStore();
6987
7099
  var defaultUserPushSubscriptionStore = new InMemoryUserPushSubscriptionStore();
6988
7100
  var defaultLeaseStore = new InMemoryLeaseStore();
6989
7101
  var defaultOutboxStore = new InMemoryOutboxStore();
7102
+ var defaultPluginDeploymentStore = new InMemoryPluginDeploymentStore();
6990
7103
  storeLatticeManager.registerLattice("default", "thread", defaultThreadStore);
6991
7104
  storeLatticeManager.registerLattice(
6992
7105
  "default",
@@ -7010,6 +7123,7 @@ storeLatticeManager.registerLattice("default", "projectRoomReadState", defaultPr
7010
7123
  storeLatticeManager.registerLattice("default", "userPushSubscription", defaultUserPushSubscriptionStore);
7011
7124
  storeLatticeManager.registerLattice("default", "lease", defaultLeaseStore);
7012
7125
  storeLatticeManager.registerLattice("default", "outbox", defaultOutboxStore);
7126
+ storeLatticeManager.registerLattice("default", "pluginDeployment", defaultPluginDeploymentStore);
7013
7127
  var defaultUserStore = new InMemoryUserStore();
7014
7128
  var defaultTenantStore = new InMemoryTenantStore();
7015
7129
  var defaultUserTenantLinkStore = new InMemoryUserTenantLinkStore();
@@ -15069,6 +15183,82 @@ var evalPlugin = {
15069
15183
  })
15070
15184
  };
15071
15185
 
15186
+ // src/sandbox_plugin/residentTransport.ts
15187
+ var DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS = 6e4;
15188
+ function isRecord3(value) {
15189
+ return typeof value === "object" && value !== null && !Array.isArray(value);
15190
+ }
15191
+ function isEnvelope(value) {
15192
+ return isRecord3(value) && typeof value.ok === "boolean";
15193
+ }
15194
+ function errorFromBody(value) {
15195
+ if (isRecord3(value) && typeof value.error === "string" && value.error.length > 0) {
15196
+ return value.error;
15197
+ }
15198
+ return void 0;
15199
+ }
15200
+ async function readJson(response) {
15201
+ try {
15202
+ return await response.json();
15203
+ } catch {
15204
+ return void 0;
15205
+ }
15206
+ }
15207
+ async function invokeResident(args) {
15208
+ const {
15209
+ baseUrl,
15210
+ endpointPath,
15211
+ serviceKey,
15212
+ payload,
15213
+ timeoutMs = DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS
15214
+ } = args;
15215
+ const fetchImpl = args.fetchImpl ?? fetch;
15216
+ const url = `${baseUrl.replace(/\/$/, "")}${endpointPath}/invoke`;
15217
+ const headers = {
15218
+ "Content-Type": "application/json"
15219
+ };
15220
+ if (serviceKey) {
15221
+ headers["Authorization"] = `Bearer ${serviceKey}`;
15222
+ }
15223
+ const controller = new AbortController();
15224
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
15225
+ try {
15226
+ const response = await fetchImpl(url, {
15227
+ method: "POST",
15228
+ headers,
15229
+ body: JSON.stringify({ ...payload, timeoutMs: payload.timeoutMs ?? timeoutMs }),
15230
+ signal: controller.signal
15231
+ });
15232
+ const parsed = await readJson(response);
15233
+ if (!response.ok) {
15234
+ return {
15235
+ ok: false,
15236
+ error: errorFromBody(parsed) ?? `resident service responded with HTTP ${response.status}`,
15237
+ status: response.status
15238
+ };
15239
+ }
15240
+ if (!isEnvelope(parsed)) {
15241
+ return {
15242
+ ok: false,
15243
+ error: "resident service returned an invalid envelope",
15244
+ status: response.status
15245
+ };
15246
+ }
15247
+ if (!parsed.ok) {
15248
+ return {
15249
+ ok: false,
15250
+ error: errorFromBody(parsed) ?? "resident handler reported failure",
15251
+ status: response.status
15252
+ };
15253
+ }
15254
+ return { ok: true, result: parsed.result, status: response.status };
15255
+ } catch (error) {
15256
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
15257
+ } finally {
15258
+ clearTimeout(timer);
15259
+ }
15260
+ }
15261
+
15072
15262
  // src/sandbox_plugin/SandboxPluginExecutor.ts
15073
15263
  var PLUGINS_ROOT = "/root/.agents/plugins";
15074
15264
  var INLINE_INPUT_LIMIT_BYTES = 48 * 1024;
@@ -15081,11 +15271,11 @@ var MAX_MAX_RESULT_BYTES = 1048576;
15081
15271
  function encode(value) {
15082
15272
  return Buffer.from(JSON.stringify(value ?? null), "utf8").toString("base64");
15083
15273
  }
15084
- function isRecord3(value) {
15274
+ function isRecord4(value) {
15085
15275
  return typeof value === "object" && value !== null && !Array.isArray(value);
15086
15276
  }
15087
- function isEnvelope(value) {
15088
- return isRecord3(value) && typeof value.ok === "boolean";
15277
+ function isEnvelope2(value) {
15278
+ return isRecord4(value) && typeof value.ok === "boolean";
15089
15279
  }
15090
15280
  function clamp(value, min, max) {
15091
15281
  return Math.min(Math.max(value, min), max);
@@ -15106,7 +15296,7 @@ function parseEnvelope(stdout) {
15106
15296
  const trimmed = stdout.trim();
15107
15297
  try {
15108
15298
  const parsed = JSON.parse(trimmed || "null");
15109
- if (isEnvelope(parsed)) return parsed;
15299
+ if (isEnvelope2(parsed)) return parsed;
15110
15300
  } catch {
15111
15301
  }
15112
15302
  const lines = stdout.split("\n");
@@ -15115,7 +15305,7 @@ function parseEnvelope(stdout) {
15115
15305
  if (!line) continue;
15116
15306
  try {
15117
15307
  const parsed = JSON.parse(line);
15118
- if (isEnvelope(parsed)) return parsed;
15308
+ if (isEnvelope2(parsed)) return parsed;
15119
15309
  } catch {
15120
15310
  }
15121
15311
  }
@@ -15128,26 +15318,258 @@ function truncate(value, cap) {
15128
15318
  while (end > 0 && (buf[end] & 192) === 128) end -= 1;
15129
15319
  return buf.subarray(0, end).toString("utf8") + "\n...[truncated]";
15130
15320
  }
15321
+ function capResult(result, cap) {
15322
+ const serialized = typeof result === "string" ? result : JSON.stringify(result ?? null);
15323
+ return Buffer.byteLength(serialized, "utf8") <= cap ? result : truncate(serialized, cap);
15324
+ }
15131
15325
  function errorFromStdout(stdout) {
15132
15326
  let text = stdout;
15133
15327
  try {
15134
15328
  const parsed = JSON.parse(stdout);
15135
- if (isRecord3(parsed)) {
15329
+ if (isRecord4(parsed)) {
15136
15330
  text = JSON.stringify(sanitize(parsed)) ?? stdout;
15137
15331
  }
15138
15332
  } catch {
15139
15333
  }
15140
15334
  return text.slice(0, 4096);
15141
15335
  }
15336
+ function buildResidentPayload(args, timeoutMs) {
15337
+ return {
15338
+ handler: args.handler,
15339
+ input: args.input,
15340
+ ...args.connection === void 0 ? {} : { connection: args.connection },
15341
+ ...args.connections === void 0 ? {} : { connections: args.connections },
15342
+ context: args.context,
15343
+ timeoutMs
15344
+ };
15345
+ }
15142
15346
  var SandboxPluginExecutor = class {
15143
15347
  /**
15144
- * @param deps - Sandbox resolver and optional logger
15348
+ * @param deps - Sandbox resolver, optional logger, and optional resident routing
15145
15349
  */
15146
15350
  constructor(deps) {
15147
15351
  this.deps = deps;
15148
15352
  }
15149
15353
  /**
15150
- * Invoke a plugin handler inside the tenant sandbox shell.
15354
+ * Invoke a plugin handler.
15355
+ *
15356
+ * Routes to the published plugin's resident service when {@link
15357
+ * SandboxPluginExecutorDeps.resident} is configured and a deployment is
15358
+ * routable (`running` with an endpoint, or a resumed `onDemand` deployment).
15359
+ * Otherwise, or when the resident call fails and degradation is enabled, runs
15360
+ * the handler as a short `node <handler>` process inside the tenant sandbox.
15361
+ *
15362
+ * @param args - Invocation arguments including manifest, handler, and input
15363
+ * @returns The normalized handler result, exit code, timing, and raw stdout
15364
+ * @throws When `args.handler` is not a safe relative path
15365
+ */
15366
+ async invoke(args) {
15367
+ const started = Date.now();
15368
+ validateRelativePath(args.handler, "handler");
15369
+ if (this.deps.resident) {
15370
+ const resident = await this.tryResident(args, this.deps.resident, started);
15371
+ if (resident) return resident;
15372
+ }
15373
+ return this.invokeShortProcess(args, started);
15374
+ }
15375
+ /**
15376
+ * Attempt to route an invocation to a published plugin's resident service.
15377
+ *
15378
+ * @param args - Invocation arguments
15379
+ * @param resident - Resident routing dependencies
15380
+ * @param started - Invocation start timestamp used for `durationMs`
15381
+ * @returns A terminal result (success, or failure with degradation disabled),
15382
+ * or `undefined` when the caller should fall back to the short-process path
15383
+ */
15384
+ async tryResident(args, resident, started) {
15385
+ const tenantId2 = args.runConfig?.tenantId ?? args.context.tenantId;
15386
+ const pluginType = args.manifest.type;
15387
+ if (!tenantId2) return void 0;
15388
+ const deployment = await this.lookupDeployment(resident, tenantId2, pluginType);
15389
+ if (!deployment) return void 0;
15390
+ const timeoutMs = clamp(args.timeoutMs ?? DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS);
15391
+ const payload = buildResidentPayload(args, timeoutMs);
15392
+ if (deployment.status === "running" && deployment.endpoint) {
15393
+ let outcome = await this.callResident(resident, deployment.endpoint, payload, timeoutMs);
15394
+ if (!outcome.ok && deployment.mode === "onDemand" && resident.ensureRunning) {
15395
+ outcome = await this.ensureRunningAndRetry(
15396
+ resident,
15397
+ tenantId2,
15398
+ pluginType,
15399
+ deployment.endpoint,
15400
+ payload,
15401
+ timeoutMs,
15402
+ outcome
15403
+ );
15404
+ }
15405
+ return this.finalizeResident(outcome, args, resident, pluginType, tenantId2, started);
15406
+ }
15407
+ if (deployment.mode === "onDemand" && resident.ensureRunning) {
15408
+ const outcome = await this.wakeOnDemand(resident, tenantId2, pluginType, payload, timeoutMs);
15409
+ if (outcome) {
15410
+ return this.finalizeResident(outcome, args, resident, pluginType, tenantId2, started);
15411
+ }
15412
+ }
15413
+ return void 0;
15414
+ }
15415
+ /**
15416
+ * Normalize a resident invocation outcome into the caller-facing result.
15417
+ *
15418
+ * @param outcome - Normalized resident transport result
15419
+ * @param args - Invocation arguments (for the result-size cap)
15420
+ * @param resident - Resident routing dependencies (degradation policy)
15421
+ * @param pluginType - Plugin type, for diagnostics
15422
+ * @param tenantId - Tenant owning the plugin, for diagnostics
15423
+ * @param started - Invocation start timestamp used for `durationMs`
15424
+ * @returns The normalized result, or `undefined` when the caller should fall
15425
+ * back to the short-process path
15426
+ */
15427
+ finalizeResident(outcome, args, resident, pluginType, tenantId2, started) {
15428
+ if (outcome.ok) {
15429
+ const cap = clamp(
15430
+ args.maxResultBytes ?? DEFAULT_MAX_RESULT_BYTES,
15431
+ MIN_MAX_RESULT_BYTES,
15432
+ MAX_MAX_RESULT_BYTES
15433
+ );
15434
+ return {
15435
+ ok: true,
15436
+ result: capResult(outcome.result, cap),
15437
+ exitCode: 0,
15438
+ httpStatus: outcome.status,
15439
+ durationMs: Date.now() - started,
15440
+ stdout: "",
15441
+ stderr: ""
15442
+ };
15443
+ }
15444
+ const error = outcome.error ?? "resident plugin invocation failed";
15445
+ if (resident.degradeToShortProcess ?? true) {
15446
+ this.deps.logger?.info(
15447
+ { event: "sandbox-plugin.resident.degrade", pluginType, tenantId: tenantId2, error },
15448
+ "resident plugin invocation failed; falling back to short process"
15449
+ );
15450
+ return void 0;
15451
+ }
15452
+ return {
15453
+ ok: false,
15454
+ error,
15455
+ exitCode: 1,
15456
+ httpStatus: outcome.status,
15457
+ durationMs: Date.now() - started,
15458
+ stdout: "",
15459
+ stderr: ""
15460
+ };
15461
+ }
15462
+ /**
15463
+ * Look up a deployment, degrading to `undefined` when the lookup throws.
15464
+ *
15465
+ * @param resident - Resident routing dependencies
15466
+ * @param tenantId - Tenant owning the plugin
15467
+ * @param pluginType - Plugin type addressed by the invocation
15468
+ * @returns The deployment, or `undefined` when absent or lookup failed
15469
+ */
15470
+ async lookupDeployment(resident, tenantId2, pluginType) {
15471
+ try {
15472
+ return await resident.getDeployment(tenantId2, pluginType);
15473
+ } catch (error) {
15474
+ this.deps.logger?.info(
15475
+ {
15476
+ event: "sandbox-plugin.resident.lookup.failed",
15477
+ pluginType,
15478
+ tenantId: tenantId2,
15479
+ error: error instanceof Error ? error.message : String(error)
15480
+ },
15481
+ "resident deployment lookup failed; falling back to short process"
15482
+ );
15483
+ return void 0;
15484
+ }
15485
+ }
15486
+ /**
15487
+ * Resume a non-running `onDemand` deployment once, then invoke it.
15488
+ *
15489
+ * Re-reads the deployment after the resume so a cold-started endpoint is
15490
+ * picked up. Never throws: a failed resume or a deployment that is still not
15491
+ * `running` with an endpoint resolves to `undefined`, letting the caller use
15492
+ * the short-process path.
15493
+ *
15494
+ * @param resident - Resident routing dependencies
15495
+ * @param tenantId - Tenant owning the plugin
15496
+ * @param pluginType - Plugin type addressed by the invocation
15497
+ * @param payload - Invocation payload
15498
+ * @param timeoutMs - Clamped per-call timeout
15499
+ * @returns The resident outcome when routed, or `undefined` to short-process
15500
+ */
15501
+ async wakeOnDemand(resident, tenantId2, pluginType, payload, timeoutMs) {
15502
+ try {
15503
+ await resident.ensureRunning?.(tenantId2, pluginType);
15504
+ } catch (error) {
15505
+ this.deps.logger?.info(
15506
+ {
15507
+ event: "sandbox-plugin.resident.ensure.failed",
15508
+ pluginType,
15509
+ tenantId: tenantId2,
15510
+ error: error instanceof Error ? error.message : String(error)
15511
+ },
15512
+ "resident service resume failed; falling back to short process"
15513
+ );
15514
+ return void 0;
15515
+ }
15516
+ const refreshed = await this.lookupDeployment(resident, tenantId2, pluginType);
15517
+ if (!refreshed || refreshed.status !== "running" || !refreshed.endpoint) {
15518
+ return void 0;
15519
+ }
15520
+ return this.callResident(resident, refreshed.endpoint, payload, timeoutMs);
15521
+ }
15522
+ /**
15523
+ * Resume an `onDemand` deployment once and retry the resident call once.
15524
+ *
15525
+ * @param resident - Resident routing dependencies
15526
+ * @param tenantId - Tenant owning the plugin
15527
+ * @param pluginType - Plugin type addressed by the invocation
15528
+ * @param endpoint - Endpoint used by the first attempt
15529
+ * @param payload - Invocation payload
15530
+ * @param timeoutMs - Clamped per-call timeout
15531
+ * @param firstOutcome - Failed first attempt, returned when resume fails
15532
+ * @returns The retry outcome, or `firstOutcome` when resume itself failed
15533
+ */
15534
+ async ensureRunningAndRetry(resident, tenantId2, pluginType, endpoint, payload, timeoutMs, firstOutcome) {
15535
+ try {
15536
+ await resident.ensureRunning?.(tenantId2, pluginType);
15537
+ } catch (error) {
15538
+ this.deps.logger?.info(
15539
+ {
15540
+ event: "sandbox-plugin.resident.ensure.failed",
15541
+ pluginType,
15542
+ tenantId: tenantId2,
15543
+ error: error instanceof Error ? error.message : String(error)
15544
+ },
15545
+ "resident service resume failed"
15546
+ );
15547
+ return firstOutcome;
15548
+ }
15549
+ const refreshed = await this.lookupDeployment(resident, tenantId2, pluginType);
15550
+ return this.callResident(resident, refreshed?.endpoint ?? endpoint, payload, timeoutMs);
15551
+ }
15552
+ /**
15553
+ * Invoke the resident host over the sandbox-service proxy path.
15554
+ *
15555
+ * @param resident - Resident routing dependencies
15556
+ * @param endpointPath - Relative proxy path
15557
+ * @param payload - Invocation payload matching the host contract
15558
+ * @param timeoutMs - Clamped per-call timeout
15559
+ * @returns The normalized resident invocation result
15560
+ */
15561
+ callResident(resident, endpointPath, payload, timeoutMs) {
15562
+ return invokeResident({
15563
+ baseUrl: resident.baseUrl,
15564
+ endpointPath,
15565
+ serviceKey: resident.serviceKey,
15566
+ payload,
15567
+ timeoutMs,
15568
+ fetchImpl: resident.fetchImpl
15569
+ });
15570
+ }
15571
+ /**
15572
+ * Invoke a plugin handler as a short `node <handler>` process.
15151
15573
  *
15152
15574
  * Resolves the sandbox, injects small input as a base64 env var (or writes a
15153
15575
  * temp `input.json` for large input), writes connection credentials to a temp
@@ -15157,12 +15579,10 @@ var SandboxPluginExecutor = class {
15157
15579
  * in all cases, including setup failures.
15158
15580
  *
15159
15581
  * @param args - Invocation arguments including manifest, handler, and input
15582
+ * @param started - Invocation start timestamp used for `durationMs`
15160
15583
  * @returns The normalized handler result, exit code, timing, and raw stdout
15161
- * @throws When `args.handler` is not a safe relative path
15162
15584
  */
15163
- async invoke(args) {
15164
- const started = Date.now();
15165
- validateRelativePath(args.handler, "handler");
15585
+ async invokeShortProcess(args, started) {
15166
15586
  const pluginDir = `${PLUGINS_ROOT}/${args.manifest.type}`;
15167
15587
  const handlerAbs = `${pluginDir}/${args.handler}`;
15168
15588
  const tenantId2 = args.runConfig?.tenantId ?? args.context.tenantId;
@@ -15250,11 +15670,9 @@ var SandboxPluginExecutor = class {
15250
15670
  }
15251
15671
  const parsed = parseEnvelope(stdout);
15252
15672
  if (parsed) {
15253
- const result = parsed.ok ? parsed.result : void 0;
15254
- const serialized = typeof result === "string" ? result : JSON.stringify(result ?? null);
15255
15673
  return {
15256
15674
  ok: parsed.ok,
15257
- result: Buffer.byteLength(serialized, "utf8") <= cap ? result : truncate(serialized, cap),
15675
+ result: capResult(parsed.ok ? parsed.result : void 0, cap),
15258
15676
  error: typeof parsed.error === "string" ? parsed.error : void 0,
15259
15677
  exitCode,
15260
15678
  durationMs,
@@ -15265,7 +15683,7 @@ var SandboxPluginExecutor = class {
15265
15683
  const text = stdout.trim();
15266
15684
  return {
15267
15685
  ok: true,
15268
- result: truncate(text, cap),
15686
+ result: capResult(text, cap),
15269
15687
  exitCode,
15270
15688
  durationMs,
15271
15689
  stdout,
@@ -15282,24 +15700,33 @@ var SandboxPluginExecutor = class {
15282
15700
  }
15283
15701
  };
15284
15702
 
15703
+ // src/sandbox_plugin/pluginDeploymentServiceRuntime.ts
15704
+ var service2 = null;
15705
+ function setPluginDeploymentService(instance) {
15706
+ service2 = instance ?? null;
15707
+ }
15708
+ function getPluginDeploymentService() {
15709
+ return service2;
15710
+ }
15711
+
15285
15712
  // src/sandbox_plugin/TenantPluginCatalog.ts
15286
15713
  var import_node_crypto3 = require("crypto");
15287
15714
 
15288
15715
  // src/sandbox_plugin/jsonSchemaToZod.ts
15289
15716
  var import_zod45 = require("zod");
15290
- function isRecord4(value) {
15717
+ function isRecord5(value) {
15291
15718
  return typeof value === "object" && value !== null && !Array.isArray(value);
15292
15719
  }
15293
15720
  function withDescription(schema6, raw) {
15294
- const description = isRecord4(raw) ? raw.description : void 0;
15721
+ const description = isRecord5(raw) ? raw.description : void 0;
15295
15722
  return typeof description === "string" ? schema6.describe(description) : schema6;
15296
15723
  }
15297
15724
  function jsonSchemaToZod(schema6) {
15298
- if (!isRecord4(schema6)) throw new Error("schema must be an object");
15725
+ if (!isRecord5(schema6)) throw new Error("schema must be an object");
15299
15726
  const type = schema6.type;
15300
15727
  switch (type) {
15301
15728
  case "object": {
15302
- const props = isRecord4(schema6.properties) ? schema6.properties : {};
15729
+ const props = isRecord5(schema6.properties) ? schema6.properties : {};
15303
15730
  const required = Array.isArray(schema6.required) ? schema6.required.filter((x) => typeof x === "string") : [];
15304
15731
  const shape = {};
15305
15732
  for (const [key5, sub] of Object.entries(props)) {
@@ -15339,7 +15766,7 @@ var DISPLAY_MODES = ["inline", "fullscreen", "pip"];
15339
15766
  var MAX_MANIFEST_BYTES = 262144;
15340
15767
  var MAX_TOOLS = 32;
15341
15768
  var MAX_PLUGINS_PER_TENANT = 50;
15342
- function isRecord5(value) {
15769
+ function isRecord6(value) {
15343
15770
  return typeof value === "object" && value !== null && !Array.isArray(value);
15344
15771
  }
15345
15772
  function isSafeHandler(path8) {
@@ -15364,7 +15791,7 @@ function validateSandboxPluginManifest(args) {
15364
15791
  const diagnostics = [];
15365
15792
  const error = (code, message, pluginType, tool68) => diagnostics.push({ level: "error", code, message, pluginType, tool: tool68 });
15366
15793
  const raw = args.raw;
15367
- if (!isRecord5(raw)) {
15794
+ if (!isRecord6(raw)) {
15368
15795
  error("MANIFEST_NOT_OBJECT", "manifest must be a JSON object", args.dirName);
15369
15796
  return { tools: [], diagnostics };
15370
15797
  }
@@ -15417,7 +15844,7 @@ function validateSandboxPluginManifest(args) {
15417
15844
  const tools = [];
15418
15845
  const seenToolNames = /* @__PURE__ */ new Set();
15419
15846
  for (const candidate of toolCandidates.slice(0, MAX_TOOLS)) {
15420
- if (!isRecord5(candidate)) {
15847
+ if (!isRecord6(candidate)) {
15421
15848
  error("TOOL_INVALID", "tool must be an object", type);
15422
15849
  continue;
15423
15850
  }
@@ -15434,7 +15861,7 @@ function validateSandboxPluginManifest(args) {
15434
15861
  error("HANDLER_MISSING", `handler "${String(candidate.handler)}" not found`, type, name);
15435
15862
  continue;
15436
15863
  }
15437
- if (!isRecord5(candidate.schema)) {
15864
+ if (!isRecord6(candidate.schema)) {
15438
15865
  error("TOOL_SCHEMA", `tool "${name}" schema must be an object`, type, name);
15439
15866
  continue;
15440
15867
  }
@@ -15448,17 +15875,17 @@ function validateSandboxPluginManifest(args) {
15448
15875
  let ui;
15449
15876
  if (candidate.ui !== void 0) {
15450
15877
  const rawUi = candidate.ui;
15451
- if (!isRecord5(rawUi) || !isSafeUiPath(rawUi.resource) || !args.handlerExists(rawUi.resource)) {
15878
+ if (!isRecord6(rawUi) || !isSafeUiPath(rawUi.resource) || !args.handlerExists(rawUi.resource)) {
15452
15879
  error("TOOL_UI", `tool "${name}" ui.resource must be an existing .html file within the plugin dir`, type, name);
15453
15880
  } else {
15454
- const csp = isRecord5(rawUi.csp) ? {
15881
+ const csp = isRecord6(rawUi.csp) ? {
15455
15882
  connectDomains: Array.isArray(rawUi.csp.connectDomains) ? rawUi.csp.connectDomains.filter((d) => typeof d === "string") : void 0,
15456
15883
  resourceDomains: Array.isArray(rawUi.csp.resourceDomains) ? rawUi.csp.resourceDomains.filter((d) => typeof d === "string") : void 0,
15457
15884
  frameDomains: Array.isArray(rawUi.csp.frameDomains) ? rawUi.csp.frameDomains.filter((d) => typeof d === "string") : void 0,
15458
15885
  baseUriDomains: Array.isArray(rawUi.csp.baseUriDomains) ? rawUi.csp.baseUriDomains.filter((d) => typeof d === "string") : void 0
15459
15886
  } : void 0;
15460
15887
  const displayMode = DISPLAY_MODES.includes(rawUi.displayMode) ? rawUi.displayMode : void 0;
15461
- const permissions = isRecord5(rawUi.permissions) ? {
15888
+ const permissions = isRecord6(rawUi.permissions) ? {
15462
15889
  camera: rawUi.permissions.camera === true ? true : void 0,
15463
15890
  microphone: rawUi.permissions.microphone === true ? true : void 0,
15464
15891
  geolocation: rawUi.permissions.geolocation === true ? true : void 0,
@@ -15488,7 +15915,7 @@ function validateSandboxPluginManifest(args) {
15488
15915
  }
15489
15916
  const skills = {};
15490
15917
  if (raw.skills !== void 0) {
15491
- if (!isRecord5(raw.skills)) {
15918
+ if (!isRecord6(raw.skills)) {
15492
15919
  error("SKILLS_INVALID", "skills must be an object", type);
15493
15920
  } else {
15494
15921
  const prefix = `${type}-`;
@@ -15503,12 +15930,12 @@ function validateSandboxPluginManifest(args) {
15503
15930
  error("SKILL_NAME", `skill "${name}" must be a safe relative path prefixed with "${prefix}"`, type);
15504
15931
  continue;
15505
15932
  }
15506
- if (!isRecord5(definition) || typeof definition.version !== "string" || typeof definition.content !== "string") {
15933
+ if (!isRecord6(definition) || typeof definition.version !== "string" || typeof definition.content !== "string") {
15507
15934
  error("SKILL_INVALID", `skill "${name}" requires string version and content`, type);
15508
15935
  continue;
15509
15936
  }
15510
15937
  if (definition.resources !== void 0) {
15511
- if (!isRecord5(definition.resources)) {
15938
+ if (!isRecord6(definition.resources)) {
15512
15939
  error("SKILL_RESOURCES", `skill "${name}" resources must be an object`, type);
15513
15940
  continue;
15514
15941
  }
@@ -15520,7 +15947,7 @@ function validateSandboxPluginManifest(args) {
15520
15947
  resourcesOk = false;
15521
15948
  break;
15522
15949
  }
15523
- if (!isRecord5(resource) || typeof resource.content !== "string") {
15950
+ if (!isRecord6(resource) || typeof resource.content !== "string") {
15524
15951
  resourcesOk = false;
15525
15952
  break;
15526
15953
  }
@@ -15536,11 +15963,11 @@ function validateSandboxPluginManifest(args) {
15536
15963
  }
15537
15964
  const agents = {};
15538
15965
  if (raw.agents !== void 0) {
15539
- if (!isRecord5(raw.agents)) {
15966
+ if (!isRecord6(raw.agents)) {
15540
15967
  error("AGENTS_INVALID", "agents must be an object", type);
15541
15968
  } else {
15542
15969
  for (const [key5, config] of Object.entries(raw.agents)) {
15543
- if (!isRecord5(config)) {
15970
+ if (!isRecord6(config)) {
15544
15971
  error("AGENT_INVALID", `agent "${key5}" must be an object`, type);
15545
15972
  continue;
15546
15973
  }
@@ -15561,7 +15988,7 @@ function validateSandboxPluginManifest(args) {
15561
15988
  }
15562
15989
  }
15563
15990
  let connection;
15564
- if (isRecord5(raw.connection)) {
15991
+ if (isRecord6(raw.connection)) {
15565
15992
  const fields = raw.connection.fields;
15566
15993
  if (!Array.isArray(fields)) {
15567
15994
  error("CONNECTION_INVALID", "connection.fields must be an array", type);
@@ -15575,7 +16002,7 @@ function validateSandboxPluginManifest(args) {
15575
16002
  for (const key5 of ["test", "discover"]) {
15576
16003
  const entry = raw.connection[key5];
15577
16004
  if (entry === void 0) continue;
15578
- const handler = isRecord5(entry) ? entry.handler : void 0;
16005
+ const handler = isRecord6(entry) ? entry.handler : void 0;
15579
16006
  if (!isSafeHandler(handler) || !args.handlerExists(handler)) {
15580
16007
  error("CONNECTION_HANDLER", `connection.${key5} handler "${String(handler)}" not found`, type);
15581
16008
  continue;
@@ -15593,8 +16020,8 @@ function validateSandboxPluginManifest(args) {
15593
16020
  version: raw.version,
15594
16021
  icon: typeof raw.icon === "string" ? raw.icon : void 0,
15595
16022
  category: typeof raw.category === "string" ? raw.category : void 0,
15596
- configSchema: isRecord5(raw.configSchema) ? raw.configSchema : void 0,
15597
- defaultConfig: isRecord5(raw.defaultConfig) ? raw.defaultConfig : void 0,
16023
+ configSchema: isRecord6(raw.configSchema) ? raw.configSchema : void 0,
16024
+ defaultConfig: isRecord6(raw.defaultConfig) ? raw.defaultConfig : void 0,
15598
16025
  connection,
15599
16026
  tools,
15600
16027
  skills: Object.keys(skills).length > 0 ? skills : void 0,
@@ -15774,7 +16201,7 @@ var STANDARD_CONNECTION_DEFAULT_CONFIG = Object.freeze({
15774
16201
  connectAll: false
15775
16202
  });
15776
16203
  var EMPTY_DEFAULT_CONFIG = Object.freeze({});
15777
- function isRecord6(value) {
16204
+ function isRecord7(value) {
15778
16205
  return typeof value === "object" && value !== null && !Array.isArray(value);
15779
16206
  }
15780
16207
  function runConfigFrom(exeConfig) {
@@ -15864,11 +16291,11 @@ async function runConnectionHandler(executor, manifest, handler, config, tenantI
15864
16291
  return { ok: false, message: result.error ?? "connection test failed" };
15865
16292
  }
15866
16293
  const value = result.result;
15867
- if (isRecord6(value) && typeof value.ok === "boolean" && typeof value.message === "string") {
16294
+ if (isRecord7(value) && typeof value.ok === "boolean" && typeof value.message === "string") {
15868
16295
  return {
15869
16296
  ok: value.ok,
15870
16297
  message: value.message,
15871
- details: isRecord6(value.details) ? value.details : void 0
16298
+ details: isRecord7(value.details) ? value.details : void 0
15872
16299
  };
15873
16300
  }
15874
16301
  return { ok: false, message: result.error ?? "connection test returned an invalid result" };
@@ -16221,13 +16648,29 @@ var TenantPluginCatalog = class {
16221
16648
 
16222
16649
  // src/sandbox_plugin/sandboxPluginRuntime.ts
16223
16650
  var catalog = null;
16651
+ function buildResidentRouting() {
16652
+ const baseUrl = process.env.MICROSANDBOX_SERVICE_URL;
16653
+ if (!baseUrl) return void 0;
16654
+ return {
16655
+ getDeployment: async (tenantId2, pluginType) => await getPluginDeploymentService()?.getDeployment(tenantId2, pluginType) ?? void 0,
16656
+ ensureRunning: async (tenantId2, pluginType) => {
16657
+ await getPluginDeploymentService()?.ensureRunning(tenantId2, pluginType);
16658
+ },
16659
+ baseUrl,
16660
+ serviceKey: process.env.MICROSANDBOX_API_KEY
16661
+ };
16662
+ }
16663
+ function buildExecutor(resolveSandbox) {
16664
+ const resident = buildResidentRouting();
16665
+ return new SandboxPluginExecutor({ resolveSandbox, ...resident ? { resident } : {} });
16666
+ }
16224
16667
  function emptyCatalog() {
16225
16668
  const resolveSandbox = async () => {
16226
16669
  throw new Error("sandbox manager not configured");
16227
16670
  };
16228
16671
  return new TenantPluginCatalog({
16229
16672
  resolveSandbox,
16230
- executor: new SandboxPluginExecutor({ resolveSandbox }),
16673
+ executor: buildExecutor(resolveSandbox),
16231
16674
  reservedTypes: () => new Set(PluginRegistry.list())
16232
16675
  });
16233
16676
  }
@@ -16244,7 +16687,7 @@ function getSandboxPluginCatalog() {
16244
16687
  const resolveSandbox = createSandboxResolver((config) => manager.getSandboxFromConfig(config));
16245
16688
  catalog = new TenantPluginCatalog({
16246
16689
  resolveSandbox,
16247
- executor: new SandboxPluginExecutor({ resolveSandbox }),
16690
+ executor: buildExecutor(resolveSandbox),
16248
16691
  reservedTypes: () => new Set(PluginRegistry.list())
16249
16692
  });
16250
16693
  return catalog;
@@ -16391,23 +16834,23 @@ function toolName(tool68) {
16391
16834
  return typeof tool68.name === "string" ? tool68.name : void 0;
16392
16835
  }
16393
16836
  function schemaHasConnectionKey(schema6) {
16394
- const definition = isRecord7(schema6._def) ? schema6._def : void 0;
16837
+ const definition = isRecord8(schema6._def) ? schema6._def : void 0;
16395
16838
  const shape = definition?.shape;
16396
16839
  const fields = typeof shape === "function" ? shape() : shape;
16397
- return isRecord7(fields) && Object.prototype.hasOwnProperty.call(fields, "connectionKey");
16840
+ return isRecord8(fields) && Object.prototype.hasOwnProperty.call(fields, "connectionKey");
16398
16841
  }
16399
16842
  function hasConnectionKeySchema(tool68) {
16400
- if (!isRecord7(tool68) || !isRecord7(tool68.schema)) return false;
16843
+ if (!isRecord8(tool68) || !isRecord8(tool68.schema)) return false;
16401
16844
  if (schemaHasConnectionKey(tool68.schema)) return true;
16402
- const definition = isRecord7(tool68.schema._def) ? tool68.schema._def : void 0;
16845
+ const definition = isRecord8(tool68.schema._def) ? tool68.schema._def : void 0;
16403
16846
  if (Array.isArray(definition?.options) && definition.options.length > 0) {
16404
16847
  return definition.options.every(
16405
- (option) => isRecord7(option) && schemaHasConnectionKey(option)
16848
+ (option) => isRecord8(option) && schemaHasConnectionKey(option)
16406
16849
  );
16407
16850
  }
16408
16851
  return false;
16409
16852
  }
16410
- function isRecord7(value) {
16853
+ function isRecord8(value) {
16411
16854
  return typeof value === "object" && value !== null && !Array.isArray(value);
16412
16855
  }
16413
16856
  var CapabilityToolRegistry = class {
@@ -16499,17 +16942,17 @@ var CapabilityToolRegistry = class {
16499
16942
  // src/middlewares/capabilityGuardMiddleware.ts
16500
16943
  var import_messages4 = require("@langchain/core/messages");
16501
16944
  var import_langchain49 = require("langchain");
16502
- function isRecord8(value) {
16945
+ function isRecord9(value) {
16503
16946
  return typeof value === "object" && value !== null && !Array.isArray(value);
16504
16947
  }
16505
16948
  function capabilityRuntimeFromRequest(request) {
16506
- if (!isRecord8(request) || !isRecord8(request.runtime)) return void 0;
16949
+ if (!isRecord9(request) || !isRecord9(request.runtime)) return void 0;
16507
16950
  const configurable = request.runtime.configurable;
16508
16951
  const context = request.runtime.context;
16509
- const runConfig = isRecord8(configurable) && isRecord8(configurable.runConfig) ? configurable.runConfig : isRecord8(context) && isRecord8(context.runConfig) ? context.runConfig : void 0;
16952
+ const runConfig = isRecord9(configurable) && isRecord9(configurable.runConfig) ? configurable.runConfig : isRecord9(context) && isRecord9(context.runConfig) ? context.runConfig : void 0;
16510
16953
  if (!runConfig) return void 0;
16511
16954
  const value = runConfig.capabilityRuntime;
16512
- if (!isRecord8(value) || typeof value.revision !== "string" || !isRecord8(value.middleware)) {
16955
+ if (!isRecord9(value) || typeof value.revision !== "string" || !isRecord9(value.middleware)) {
16513
16956
  return void 0;
16514
16957
  }
16515
16958
  return value;
@@ -16521,10 +16964,10 @@ function hasActiveRuntimePolicy(runtime) {
16521
16964
  return Object.keys(runtime.middleware).length > 0;
16522
16965
  }
16523
16966
  function tenantIdFromRequest(request) {
16524
- if (!isRecord8(request) || !isRecord8(request.runtime)) return void 0;
16967
+ if (!isRecord9(request) || !isRecord9(request.runtime)) return void 0;
16525
16968
  const configurable = request.runtime.configurable;
16526
16969
  const context = request.runtime.context;
16527
- const runConfig = isRecord8(configurable) && isRecord8(configurable.runConfig) ? configurable.runConfig : isRecord8(context) && isRecord8(context.runConfig) ? context.runConfig : void 0;
16970
+ const runConfig = isRecord9(configurable) && isRecord9(configurable.runConfig) ? configurable.runConfig : isRecord9(context) && isRecord9(context.runConfig) ? context.runConfig : void 0;
16528
16971
  return runConfig && typeof runConfig.tenantId === "string" ? runConfig.tenantId : void 0;
16529
16972
  }
16530
16973
  async function standardConnectionAllowed(request, capabilityType, config) {
@@ -16532,8 +16975,8 @@ async function standardConnectionAllowed(request, capabilityType, config) {
16532
16975
  const hasConnectAll = Object.prototype.hasOwnProperty.call(config, "connectAll");
16533
16976
  if (!hasConnections && (!hasConnectAll || config.connectAll !== true)) return false;
16534
16977
  const tenantId2 = tenantIdFromRequest(request);
16535
- const resource = isRecord8(request.toolCall) && isRecord8(request.toolCall.args) ? request.toolCall.args.connectionKey : void 0;
16536
- const hasConnectionKey = isRecord8(request.toolCall) && isRecord8(request.toolCall.args) && Object.prototype.hasOwnProperty.call(
16978
+ const resource = isRecord9(request.toolCall) && isRecord9(request.toolCall.args) ? request.toolCall.args.connectionKey : void 0;
16979
+ const hasConnectionKey = isRecord9(request.toolCall) && isRecord9(request.toolCall.args) && Object.prototype.hasOwnProperty.call(
16537
16980
  request.toolCall.args,
16538
16981
  "connectionKey"
16539
16982
  );
@@ -16599,10 +17042,10 @@ function createCapabilityGuardMiddleware(registry4) {
16599
17042
  toolName: toolName2
16600
17043
  });
16601
17044
  }
16602
- const standardConfig = isRecord8(policy.config) ? policy.config : {};
17045
+ const standardConfig = isRecord9(policy.config) ? policy.config : {};
16603
17046
  if (registry4.isStandardConnectionTool(toolName2) && !await standardConnectionAllowed(request, metadata.capabilityType, standardConfig)) {
16604
17047
  const args2 = request.toolCall.args;
16605
- const resource = isRecord8(args2) && typeof args2.connectionKey === "string" ? args2.connectionKey : void 0;
17048
+ const resource = isRecord9(args2) && typeof args2.connectionKey === "string" ? args2.connectionKey : void 0;
16606
17049
  return denialMessage(request.toolCall, {
16607
17050
  code: "RESOURCE_NOT_ALLOWED",
16608
17051
  message: typeof resource === "string" ? `Resource '${resource}' is not allowed by the capability policy` : "Resource selector 'connectionKey' must be a permitted string",
@@ -16615,8 +17058,8 @@ function createCapabilityGuardMiddleware(registry4) {
16615
17058
  const selector = metadata.resourceSelector;
16616
17059
  const args = request.toolCall.args;
16617
17060
  if (selector) {
16618
- const resource = isRecord8(args) ? args[selector.argumentField] : void 0;
16619
- const config = isRecord8(policy.config) ? policy.config : {};
17061
+ const resource = isRecord9(args) ? args[selector.argumentField] : void 0;
17062
+ const config = isRecord9(policy.config) ? policy.config : {};
16620
17063
  const hasExplicitResources = Object.prototype.hasOwnProperty.call(config, selector.configField);
16621
17064
  const allowsAll = !hasExplicitResources && selector.allowAllField !== void 0 && config[selector.allowAllField] === true;
16622
17065
  const allowedResources = config[selector.configField];
@@ -32391,6 +32834,7 @@ function assistantToConfig(assistant) {
32391
32834
  prompt: typeof assistant.graphDefinition === "string" ? assistant.graphDefinition : JSON.stringify(assistant.graphDefinition)
32392
32835
  };
32393
32836
  Object.assign(config, graphDef);
32837
+ if (assistant.ownerUserId !== void 0) config.ownerUserId = assistant.ownerUserId;
32394
32838
  return config;
32395
32839
  }
32396
32840
  var _AgentLatticeManager = class _AgentLatticeManager extends BaseLatticeManager {
@@ -34788,6 +35232,9 @@ var MicrosandboxRemoteInstance = class {
34788
35232
  }
34789
35233
  return "unknown";
34790
35234
  }
35235
+ async getEndpoint(port) {
35236
+ return this.client.getEndpoint(this.name, port);
35237
+ }
34791
35238
  /**
34792
35239
  * Pack a sandbox folder into a sibling ZIP through the microsandbox shell.
34793
35240
  *
@@ -34854,6 +35301,19 @@ var MicrosandboxServiceClient = class {
34854
35301
  method: "GET"
34855
35302
  });
34856
35303
  }
35304
+ /**
35305
+ * Resolve the endpoint for a published guest port of a sandbox.
35306
+ *
35307
+ * @param name - Sandbox name
35308
+ * @param port - Guest port published via `ensureSandbox({ exposePorts })`
35309
+ * @returns The scheme-less endpoint string and optional headers
35310
+ */
35311
+ async getEndpoint(name, port) {
35312
+ return this.request(
35313
+ `/api/sandboxes/${encodeURIComponent(name)}/endpoints/${port}`,
35314
+ { method: "GET" }
35315
+ );
35316
+ }
34857
35317
  async readFile(sandboxName, path8) {
34858
35318
  return this.request("/api/files/read", {
34859
35319
  method: "POST",
@@ -35118,9 +35578,9 @@ var MicrosandboxRemoteProvider = class {
35118
35578
  }
35119
35579
  buildEnsureInput(config) {
35120
35580
  const defaultMicrosandboxRemoteConfig = getDefaultMicrosandboxRemoteConfig();
35121
- const image = this.config.image ?? defaultMicrosandboxRemoteConfig.image;
35581
+ const image = config.image ?? this.config.image ?? defaultMicrosandboxRemoteConfig.image;
35122
35582
  console.log(
35123
- `[MicrosandboxRemote] buildEnsureInput image=${image} from=${this.config.image ? "providerConfig" : "defaultConfig"}`
35583
+ `[MicrosandboxRemote] buildEnsureInput image=${image} from=${config.image ? "runConfig" : this.config.image ? "providerConfig" : "defaultConfig"}`
35124
35584
  );
35125
35585
  return {
35126
35586
  image,
@@ -35130,7 +35590,10 @@ var MicrosandboxRemoteProvider = class {
35130
35590
  ...this.config.env,
35131
35591
  ...buildSandboxMetadataEnv(config)
35132
35592
  },
35133
- volumes: config?.volumes ?? this.buildDefaultVolumes(config)
35593
+ volumes: config?.volumes ?? this.buildDefaultVolumes(config),
35594
+ entrypoint: config.entrypoint,
35595
+ timeoutSeconds: config.timeoutSeconds,
35596
+ exposePorts: config.exposePorts
35134
35597
  };
35135
35598
  }
35136
35599
  buildDefaultVolumes(config) {
@@ -36856,6 +37319,157 @@ async function connectAllChannels(getAdapter, options = {}) {
36856
37319
  }
36857
37320
  }
36858
37321
  }
37322
+ async function disconnectAllChannels(getAdapter) {
37323
+ const store = getStoreLattice("default", "channelInstallation").store;
37324
+ const installations = await store.getAllInstallations();
37325
+ console.log(`[disconnectAllChannels] total=${installations.length} enabled=${installations.filter((i) => i.enabled).length}`);
37326
+ for (const inst of installations) {
37327
+ if (!inst.enabled) continue;
37328
+ const adapter = getAdapter(inst.channel);
37329
+ if (!adapter?.disconnect) continue;
37330
+ try {
37331
+ await adapter.disconnect(inst.id);
37332
+ } catch (error) {
37333
+ console.error(`[disconnectAllChannels] disconnect failed: channel=${inst.channel} tenant=${inst.tenantId} id=${inst.id}`, error);
37334
+ }
37335
+ }
37336
+ }
37337
+
37338
+ // src/channel/ChannelConnectionLeader.ts
37339
+ var import_node_crypto8 = require("crypto");
37340
+ var LEASE_KEY = "leader:channel-connections";
37341
+ var ChannelConnectionLeader = class {
37342
+ constructor(deps) {
37343
+ this.holder = null;
37344
+ this.connected = false;
37345
+ this.attempted = false;
37346
+ this.inFlight = null;
37347
+ this.timer = null;
37348
+ this.running = false;
37349
+ this.stopped = false;
37350
+ this.deps = deps;
37351
+ this.ttlMs = deps.ttlMs ?? 6e4;
37352
+ this.retryMs = deps.retryMs ?? Math.max(5e3, Math.floor(this.ttlMs / 3));
37353
+ }
37354
+ /** Starts the ownership loop (immediate tick, then every `retryMs`). */
37355
+ start() {
37356
+ if (this.timer || this.stopped) return;
37357
+ void this.tickOnce();
37358
+ this.timer = setInterval(() => {
37359
+ void this.tickOnce();
37360
+ }, this.retryMs);
37361
+ this.timer.unref?.();
37362
+ }
37363
+ /**
37364
+ * Releases ownership (if held) and disconnects.
37365
+ *
37366
+ * @returns A promise that settles after teardown completes.
37367
+ * @remarks Terminal: a stopped leader does not restart.
37368
+ */
37369
+ async stop() {
37370
+ this.stopped = true;
37371
+ if (this.timer) {
37372
+ clearInterval(this.timer);
37373
+ this.timer = null;
37374
+ }
37375
+ if (this.inFlight) await this.inFlight.catch(() => void 0);
37376
+ if (this.holder) {
37377
+ const holder = this.holder;
37378
+ this.holder = null;
37379
+ await this.deps.leaseStore.release(LEASE_KEY, holder).catch((error) => {
37380
+ this.deps.log?.warn("Channel leader lease release failed", {
37381
+ error: error instanceof Error ? error.message : String(error)
37382
+ });
37383
+ });
37384
+ }
37385
+ await this.disconnectChannels();
37386
+ }
37387
+ /**
37388
+ * Runs one ownership/health step. Public for deterministic tests.
37389
+ *
37390
+ * @returns A promise that settles when this step completes.
37391
+ */
37392
+ async tickOnce() {
37393
+ if (this.running || this.stopped) return;
37394
+ this.running = true;
37395
+ const run = this.runTick();
37396
+ this.inFlight = run;
37397
+ try {
37398
+ await run;
37399
+ } finally {
37400
+ if (this.inFlight === run) this.inFlight = null;
37401
+ this.running = false;
37402
+ }
37403
+ }
37404
+ /**
37405
+ * Performs one acquire/renew/step decision.
37406
+ *
37407
+ * @remarks A transient lease-store error while renewing keeps the current
37408
+ * holder and connections intact (retry next tick); only a confirmed
37409
+ * `renewed === false` triggers teardown.
37410
+ */
37411
+ async runTick() {
37412
+ if (this.holder) {
37413
+ let renewed = false;
37414
+ let renewFailed = false;
37415
+ try {
37416
+ renewed = await this.deps.leaseStore.renew(LEASE_KEY, this.holder, this.ttlMs);
37417
+ } catch (error) {
37418
+ renewFailed = true;
37419
+ this.deps.log?.warn("Channel leader lease renew failed", {
37420
+ error: error instanceof Error ? error.message : String(error)
37421
+ });
37422
+ }
37423
+ if (renewFailed) return;
37424
+ if (renewed) {
37425
+ if (!this.connected) await this.connectChannels();
37426
+ return;
37427
+ }
37428
+ await this.loseLeadership();
37429
+ }
37430
+ await this.tryAcquire();
37431
+ }
37432
+ async tryAcquire() {
37433
+ const holder = `${this.deps.owner}#${(0, import_node_crypto8.randomUUID)()}`;
37434
+ const acquired = await this.deps.leaseStore.tryAcquire(LEASE_KEY, holder, this.ttlMs).catch((error) => {
37435
+ this.deps.log?.warn("Channel leader lease acquire failed", {
37436
+ error: error instanceof Error ? error.message : String(error)
37437
+ });
37438
+ return false;
37439
+ });
37440
+ if (!acquired) return;
37441
+ this.holder = holder;
37442
+ await this.connectChannels();
37443
+ }
37444
+ async loseLeadership() {
37445
+ this.holder = null;
37446
+ await this.disconnectChannels();
37447
+ }
37448
+ async connectChannels() {
37449
+ this.attempted = true;
37450
+ try {
37451
+ await this.deps.connect();
37452
+ this.connected = true;
37453
+ } catch (error) {
37454
+ this.connected = false;
37455
+ this.deps.log?.warn("Channel leader connect failed", {
37456
+ error: error instanceof Error ? error.message : String(error)
37457
+ });
37458
+ }
37459
+ }
37460
+ async disconnectChannels() {
37461
+ if (!this.attempted) return;
37462
+ this.attempted = false;
37463
+ this.connected = false;
37464
+ try {
37465
+ await this.deps.disconnect();
37466
+ } catch (error) {
37467
+ this.deps.log?.warn("Channel leader disconnect failed", {
37468
+ error: error instanceof Error ? error.message : String(error)
37469
+ });
37470
+ }
37471
+ }
37472
+ };
36859
37473
 
36860
37474
  // src/eval_lattice/LatticeEval.ts
36861
37475
  var import_messages7 = require("@langchain/core/messages");
@@ -38316,7 +38930,7 @@ function getCoordinationLeaseStore() {
38316
38930
  }
38317
38931
 
38318
38932
  // src/services/withLease.ts
38319
- var import_node_crypto8 = require("crypto");
38933
+ var import_node_crypto9 = require("crypto");
38320
38934
  var LeaseAcquisitionTimeoutError = class extends Error {
38321
38935
  /**
38322
38936
  * @param key Lease key that could not be acquired.
@@ -38341,7 +38955,7 @@ async function withLease(store, key5, ownerLabel, fn, options = {}) {
38341
38955
  const acquireTimeoutMs = options.acquireTimeoutMs ?? 1e4;
38342
38956
  const retryDelayMs = options.retryDelayMs ?? 50;
38343
38957
  const heartbeatMs = options.heartbeatMs ?? Math.max(1e3, Math.floor(ttlMs / 3));
38344
- const holder = `${ownerLabel}#${(0, import_node_crypto8.randomUUID)()}`;
38958
+ const holder = `${ownerLabel}#${(0, import_node_crypto9.randomUUID)()}`;
38345
38959
  const deadline = Date.now() + acquireTimeoutMs;
38346
38960
  for (; ; ) {
38347
38961
  if (await store.tryAcquire(key5, holder, ttlMs)) break;
@@ -38819,11 +39433,11 @@ function snapshotDate(value) {
38819
39433
  }
38820
39434
 
38821
39435
  // src/services/TaskLifecycleService.ts
38822
- var import_node_crypto10 = require("crypto");
39436
+ var import_node_crypto11 = require("crypto");
38823
39437
  var import_protocols31 = require("@axiom-lattice/protocols");
38824
39438
 
38825
39439
  // src/middlewares/taskBelief.ts
38826
- var import_node_crypto9 = require("crypto");
39440
+ var import_node_crypto10 = require("crypto");
38827
39441
  function buildBeliefCompletionEventKey(taskId, result, impacts) {
38828
39442
  const canonical = JSON.stringify({
38829
39443
  beliefImpact: impacts.map((impact) => ({
@@ -38834,7 +39448,7 @@ function buildBeliefCompletionEventKey(taskId, result, impacts) {
38834
39448
  })),
38835
39449
  result: result.trim()
38836
39450
  });
38837
- return (0, import_node_crypto9.createHash)("sha256").update(canonical).digest("hex");
39451
+ return (0, import_node_crypto10.createHash)("sha256").update(canonical).digest("hex");
38838
39452
  }
38839
39453
  function buildCompletionEvidenceDetail(result, impacts, eventKey) {
38840
39454
  return { result, beliefImpact: impacts, eventKey };
@@ -39037,7 +39651,7 @@ function canonicalJson(value) {
39037
39651
  return JSON.stringify(value);
39038
39652
  }
39039
39653
  function sha256(value) {
39040
- return (0, import_node_crypto10.createHash)("sha256").update(canonicalJson(value)).digest("hex");
39654
+ return (0, import_node_crypto11.createHash)("sha256").update(canonicalJson(value)).digest("hex");
39041
39655
  }
39042
39656
  function sameEvidenceDetail(detail, taskId, result, impacts, eventKey, beliefOwner) {
39043
39657
  const storedOwner = detail.beliefOwner;
@@ -41115,7 +41729,7 @@ function createTaskLifecycleService(deps) {
41115
41729
  }
41116
41730
 
41117
41731
  // src/services/TaskExecutionService.ts
41118
- var import_node_crypto12 = require("crypto");
41732
+ var import_node_crypto13 = require("crypto");
41119
41733
  var import_node_util3 = require("util");
41120
41734
  var import_protocols32 = require("@axiom-lattice/protocols");
41121
41735
 
@@ -41438,13 +42052,13 @@ async function reconcileAllTenantTaskRuns(input) {
41438
42052
  }
41439
42053
 
41440
42054
  // src/services/taskRunLease.ts
41441
- var import_node_crypto11 = require("crypto");
42055
+ var import_node_crypto12 = require("crypto");
41442
42056
  function taskRunLeaseKey(taskId) {
41443
42057
  return `task-run:${taskId}`;
41444
42058
  }
41445
42059
  async function tryAcquireTaskRun(input) {
41446
42060
  const key5 = taskRunLeaseKey(input.taskId);
41447
- const holder = `${input.owner}#${(0, import_node_crypto11.randomUUID)()}`;
42061
+ const holder = `${input.owner}#${(0, import_node_crypto12.randomUUID)()}`;
41448
42062
  if (!await input.store.tryAcquire(key5, holder, input.ttlMs)) return null;
41449
42063
  const heartbeatMs = input.heartbeatMs ?? Math.max(1e3, Math.floor(input.ttlMs / 3));
41450
42064
  let released = false;
@@ -41811,11 +42425,11 @@ function createTaskExecutionService(deps) {
41811
42425
  let threadId;
41812
42426
  let runId;
41813
42427
  try {
41814
- threadId = storedThreadId ?? (deps.createThreadId ?? import_node_crypto12.randomUUID)();
42428
+ threadId = storedThreadId ?? (deps.createThreadId ?? import_node_crypto13.randomUUID)();
41815
42429
  if (task.status === "interrupted" && reuseCurrentRun) {
41816
42430
  runId = storedRunId;
41817
42431
  } else if (task.status === "interrupted" || task.status === "failed") {
41818
- runId = (deps.createRunId ?? import_node_crypto12.randomUUID)();
42432
+ runId = (deps.createRunId ?? import_node_crypto13.randomUUID)();
41819
42433
  }
41820
42434
  } catch (error) {
41821
42435
  return {
@@ -42074,11 +42688,11 @@ function createTaskExecutionService(deps) {
42074
42688
  }
42075
42689
 
42076
42690
  // src/services/leadership.ts
42077
- var import_node_crypto13 = require("crypto");
42691
+ var import_node_crypto14 = require("crypto");
42078
42692
  async function runAsLeader(store, name, fn, options = {}) {
42079
42693
  const key5 = `leader:${name}`;
42080
42694
  const owner2 = options.owner ?? getReplicaId();
42081
- const holder = `${owner2}#${(0, import_node_crypto13.randomUUID)()}`;
42695
+ const holder = `${owner2}#${(0, import_node_crypto14.randomUUID)()}`;
42082
42696
  const ttlMs = options.ttlMs ?? 6e4;
42083
42697
  if (!await store.tryAcquire(key5, holder, ttlMs)) return { ran: false };
42084
42698
  const heartbeat = setInterval(() => {
@@ -43311,16 +43925,16 @@ function createAskUserToClarifyTool() {
43311
43925
  }
43312
43926
 
43313
43927
  // src/middlewares/askUserClarifyMiddleware.ts
43314
- function isRecord9(value) {
43928
+ function isRecord10(value) {
43315
43929
  return typeof value === "object" && value !== null && !Array.isArray(value);
43316
43930
  }
43317
43931
  function isProjectRoomRun(request) {
43318
- if (!isRecord9(request) || !isRecord9(request.runtime)) return false;
43932
+ if (!isRecord10(request) || !isRecord10(request.runtime)) return false;
43319
43933
  const runtime = request.runtime;
43320
- const configurable = isRecord9(runtime.configurable) ? runtime.configurable.runConfig : void 0;
43321
- const context = isRecord9(runtime.context) ? runtime.context.runConfig : void 0;
43322
- const runConfig = isRecord9(configurable) ? configurable : isRecord9(context) ? context : void 0;
43323
- return isRecord9(runConfig?.projectRoom);
43934
+ const configurable = isRecord10(runtime.configurable) ? runtime.configurable.runConfig : void 0;
43935
+ const context = isRecord10(runtime.context) ? runtime.context.runConfig : void 0;
43936
+ const runConfig = isRecord10(configurable) ? configurable : isRecord10(context) ? context : void 0;
43937
+ return isRecord10(runConfig?.projectRoom);
43324
43938
  }
43325
43939
  function createAskUserClarifyMiddleware() {
43326
43940
  return (0, import_langchain79.createMiddleware)({
@@ -44760,15 +45374,16 @@ function executorString(value) {
44760
45374
  }
44761
45375
  async function renderAvailableExecutors(rc, tenantId2) {
44762
45376
  const authority = classifyTaskAuthority(rc);
44763
- const governed = authority.kind === "projectRoom" || authority.kind === "projectTask";
44764
- const projectId = governed ? (authority.trusted.projectTask ?? authority.trusted.projectRoom)?.projectId : void 0;
44765
- const cacheKey = `${tenantId2}:${governed ? `project:${projectId ?? ""}` : "ordinary"}`;
45377
+ const trusted = authority.kind === "projectRoom" || authority.kind === "projectTask" ? authority.trusted : void 0;
45378
+ const useRoster = trusted !== void 0 && !trustedRosterExempt(trusted);
45379
+ const projectId = trusted ? (trusted.projectTask ?? trusted.projectRoom)?.projectId : void 0;
45380
+ const cacheKey = `${tenantId2}:${useRoster ? `project:${projectId ?? ""}` : "tenant"}`;
44766
45381
  const cached2 = executorCache.get(cacheKey);
44767
45382
  const now = Date.now();
44768
45383
  if (cached2 && now - cached2.at < EXECUTOR_CACHE_TTL_MS) return cached2.text;
44769
45384
  const lines = [];
44770
45385
  try {
44771
- if (governed && projectId) {
45386
+ if (useRoster && projectId) {
44772
45387
  const store = getStoreLattice("default", "projectBotMembership").store;
44773
45388
  if (typeof store.list === "function") {
44774
45389
  const bots = await store.list(tenantId2, projectId);
@@ -44783,11 +45398,15 @@ async function renderAvailableExecutors(rc, tenantId2) {
44783
45398
  }
44784
45399
  }
44785
45400
  } else {
45401
+ const exempt = trusted !== void 0 && trustedRosterExempt(trusted) ? trusted : void 0;
45402
+ const excluded = exempt ? await excludedRosterExecutors(exempt) : /* @__PURE__ */ new Set();
44786
45403
  const configs = await agentLatticeManager.getAllAgentConfigsByTenant(tenantId2);
44787
45404
  for (const config of configs.slice(0, 40)) {
44788
45405
  const row = config;
44789
45406
  const id = executorString(row.key);
44790
45407
  if (!id) continue;
45408
+ if (row.ownerUserId !== void 0) continue;
45409
+ if (excluded.has(id)) continue;
44791
45410
  const name = executorString(row.name);
44792
45411
  const description = executorString(row.description);
44793
45412
  lines.push(`- ${id}${name ? ` \u2014 ${name}` : ""}${description ? ` (${description.slice(0, 120)})` : ""}`);
@@ -44799,7 +45418,7 @@ async function renderAvailableExecutors(rc, tenantId2) {
44799
45418
  executorCache.set(cacheKey, { at: now, text: "" });
44800
45419
  return "";
44801
45420
  }
44802
- const header = governed ? "## Available task executors (Project roster)" : "## Available task executors";
45421
+ const header = useRoster ? "## Available task executors (Project roster)" : "## Available task executors";
44803
45422
  const text = `
44804
45423
 
44805
45424
  ${header}
@@ -44811,6 +45430,22 @@ ${lines.join("\n")}
44811
45430
  executorCache.set(cacheKey, { at: now, text });
44812
45431
  return text;
44813
45432
  }
45433
+ async function excludedRosterExecutors(trusted) {
45434
+ const excluded = /* @__PURE__ */ new Set();
45435
+ const context = trusted.projectTask ?? trusted.projectRoom;
45436
+ if (!context) return excluded;
45437
+ try {
45438
+ const store = getStoreLattice("default", "projectBotMembership").store;
45439
+ if (typeof store.list !== "function") return excluded;
45440
+ const rows = await store.list(context.tenantId, context.projectId);
45441
+ for (const row of Array.isArray(rows) ? rows : []) {
45442
+ const record = row;
45443
+ if (record.status !== "active" && typeof record.assistantId === "string") excluded.add(record.assistantId);
45444
+ }
45445
+ } catch {
45446
+ }
45447
+ return excluded;
45448
+ }
44814
45449
  function delegatedTaskScopeError(taskId) {
44815
45450
  return JSON.stringify({
44816
45451
  success: false,
@@ -44860,7 +45495,7 @@ function trustedRosterScope(trusted) {
44860
45495
  };
44861
45496
  }
44862
45497
  function trustedRosterExempt(trusted) {
44863
- return trusted.projectTask?.rosterExempt === true;
45498
+ return trusted.projectTask?.rosterExempt === true || trusted.projectRoom?.rosterExempt === true;
44864
45499
  }
44865
45500
  async function requireTrustedOwner(trusted, targetAssistantId) {
44866
45501
  const context = trusted.projectTask ?? trusted.projectRoom;
@@ -44876,6 +45511,17 @@ async function requireTrustedOwner(trusted, targetAssistantId) {
44876
45511
  targetAssistantId
44877
45512
  });
44878
45513
  }
45514
+ async function rosterExemptOwnerError(tenantId2, targetAssistantId) {
45515
+ try {
45516
+ const lattice = await agentLatticeManager.getOrLoadWithTenant(tenantId2, targetAssistantId);
45517
+ if (lattice && lattice.config.ownerUserId === void 0) return void 0;
45518
+ } catch {
45519
+ }
45520
+ return projectPolicyError(new ProjectTaskOwnerPolicyError(
45521
+ "PROJECT_TASK_OWNER_INACTIVE",
45522
+ "Target Project task owner is not a shared agent of this tenant."
45523
+ ));
45524
+ }
44879
45525
  function trustedTaskContextFailure(task, trusted) {
44880
45526
  if (task.tenantId === trusted.tenantId && task.workspaceId === trusted.workspaceId && task.projectId === trusted.projectId) return void 0;
44881
45527
  return JSON.stringify({
@@ -45480,6 +46126,12 @@ function createTaskMiddleware(options = {}) {
45480
46126
  "Target Project task owner is not active in the exact roster."
45481
46127
  ));
45482
46128
  }
46129
+ } else {
46130
+ const ownerError = await rosterExemptOwnerError(
46131
+ (trustedProject.projectTask ?? trustedProject.projectRoom)?.tenantId ?? tenantId2,
46132
+ ownerId
46133
+ );
46134
+ if (ownerError) return ownerError;
45483
46135
  }
45484
46136
  }
45485
46137
  if (!delegatedTaskId && (typeof effectiveOwnerId !== "string" || effectiveOwnerId.length === 0)) {
@@ -45541,6 +46193,12 @@ function createTaskMiddleware(options = {}) {
45541
46193
  } catch (error) {
45542
46194
  return projectPolicyError(error);
45543
46195
  }
46196
+ } else {
46197
+ const ownerError = await rosterExemptOwnerError(
46198
+ (trustedProject.projectTask ?? trustedProject.projectRoom)?.tenantId ?? tenantId2,
46199
+ ownerId
46200
+ );
46201
+ if (ownerError) return ownerError;
45544
46202
  }
45545
46203
  }
45546
46204
  return store.create({
@@ -46588,7 +47246,7 @@ function createTaskMiddleware(options = {}) {
46588
47246
  }
46589
47247
  const callerAssistantId = rc.assistant_id;
46590
47248
  const agentId = input.agentId === "general-purpose" && callerAssistantId ? `${callerAssistantId}-general-purpose` : input.agentId;
46591
- const service2 = createTaskExecutionService({
47249
+ const service3 = createTaskExecutionService({
46592
47250
  taskStore: getTaskStore(),
46593
47251
  workItemStore: getTaskWorkItemStore(),
46594
47252
  resolveAgent: async (targetTenantId, agentId2) => !!await agentLatticeManager.getOrLoadWithTenant(targetTenantId, agentId2),
@@ -46613,7 +47271,7 @@ function createTaskMiddleware(options = {}) {
46613
47271
  }
46614
47272
  });
46615
47273
  const trustedCaller = authority.kind === "projectTask" || authority.kind === "projectRoom" ? authority.trusted.projectTask ?? authority.trusted.projectRoom : void 0;
46616
- return JSON.stringify(await service2.start({
47274
+ return JSON.stringify(await service3.start({
46617
47275
  tenantId: tenantId2,
46618
47276
  workspaceId: rc.workspaceId,
46619
47277
  projectId: rc.projectId,
@@ -48130,6 +48788,24 @@ function snapshotMembership3(value) {
48130
48788
  function snapshotRows(value) {
48131
48789
  return (0, import_protocols37.snapshotExactArray)(value) ?? [];
48132
48790
  }
48791
+ async function rosterExemptMembershipProjection(scope) {
48792
+ const excluded = /* @__PURE__ */ new Set();
48793
+ const roles = /* @__PURE__ */ new Map();
48794
+ try {
48795
+ const rows = snapshotRows(await getStoreLattice("default", "projectBotMembership").store.list(scope.tenantId, scope.projectId));
48796
+ for (const row of rows) {
48797
+ const member = snapshotMembership3(row);
48798
+ if (!member) continue;
48799
+ if (member.status !== "active") {
48800
+ excluded.add(member.assistantId);
48801
+ continue;
48802
+ }
48803
+ roles.set(member.assistantId, member.role);
48804
+ }
48805
+ } catch {
48806
+ }
48807
+ return { excluded, roles };
48808
+ }
48133
48809
  async function hasActiveCurrentMembership(scope) {
48134
48810
  const memberships = getStoreLattice("default", "projectBotMembership").store;
48135
48811
  const member = snapshotMembership3(await memberships.findById(scope.tenantId, scope.membershipId));
@@ -48146,11 +48822,11 @@ function createProjectRoomMiddleware() {
48146
48822
  if (!scope) return CONTEXT_REQUIRED;
48147
48823
  const digest = (0, import_crypto10.createHash)("sha256").update(text).digest("hex");
48148
48824
  try {
48149
- const service2 = new RoomAgentMessageService({
48825
+ const service3 = new RoomAgentMessageService({
48150
48826
  memberships: getStoreLattice("default", "projectBotMembership").store,
48151
48827
  messages: getStoreLattice("default", "projectRoomMessage").store
48152
48828
  });
48153
- await service2.post({
48829
+ await service3.post({
48154
48830
  tenantId: scope.tenantId,
48155
48831
  workspaceId: scope.workspaceId,
48156
48832
  projectId: scope.projectId,
@@ -48176,6 +48852,25 @@ function createProjectRoomMiddleware() {
48176
48852
  if (!scope) return CONTEXT_REQUIRED;
48177
48853
  try {
48178
48854
  if (!await hasActiveCurrentMembership(scope)) return CONTEXT_REQUIRED;
48855
+ if (scope.rosterExempt === true) {
48856
+ const { excluded, roles } = await rosterExemptMembershipProjection(scope);
48857
+ const configs = await agentLatticeManager.getAllAgentConfigsByTenant(scope.tenantId);
48858
+ const members2 = configs.filter((entry) => entry.ownerUserId === void 0).filter((entry) => {
48859
+ const key5 = entry.key;
48860
+ return typeof key5 === "string" && !excluded.has(key5);
48861
+ }).map((entry) => {
48862
+ const row = entry;
48863
+ const name = typeof row.name === "string" ? row.name : "";
48864
+ const assistantId = typeof row.key === "string" ? row.key : "";
48865
+ return {
48866
+ assistantId,
48867
+ mentionName: name,
48868
+ title: name,
48869
+ role: roles.get(assistantId) ?? "specialist"
48870
+ };
48871
+ }).filter((member) => member.assistantId !== "" && member.mentionName !== "").sort((left, right) => left.assistantId < right.assistantId ? -1 : left.assistantId > right.assistantId ? 1 : 0);
48872
+ return JSON.stringify({ success: true, members: members2 });
48873
+ }
48179
48874
  const rows = await getStoreLattice("default", "projectBotMembership").store.list(scope.tenantId, scope.projectId);
48180
48875
  const members = snapshotRows(rows).map(snapshotMembership3).filter((member) => member !== void 0 && member.status === "active" && member.tenantId === scope.tenantId && member.workspaceId === scope.workspaceId && member.projectId === scope.projectId && member.roomId === scope.roomId).sort((left, right) => left.joinedAt.getTime() - right.joinedAt.getTime() || (left.id < right.id ? -1 : left.id > right.id ? 1 : 0)).map((member) => ({
48181
48876
  assistantId: member.assistantId,
@@ -48923,6 +49618,70 @@ Fall back to \`execute_code\`/\`shell\` to run \`node handlers/x.js\` manually w
48923
49618
  \`PLUGIN_INPUT\` and a synthetic \`PLUGIN_CONNECTION_FILE\` to reproduce logic bugs.
48924
49619
  A synthetic fixture only verifies plumbing \u2014 a real upstream call requires a real
48925
49620
  \`connectionKey\` configured by a human.
49621
+ `,
49622
+ resources: {}
49623
+ },
49624
+ "sandbox-plugins-deploy": {
49625
+ version: "1.0.0",
49626
+ content: `---
49627
+ name: sandbox-plugins-deploy
49628
+ description: Deploy a validated tenant plugin as a resident service (alwaysOn vs onDemand), check its status, and undeploy it. Trigger: after a plugin's tests pass and it should run as a long-lived service, or when inspecting/removing a deployment.
49629
+ ---
49630
+
49631
+ # Deploying a Plugin as a Resident Service
49632
+
49633
+ By default a plugin handler runs as a short-lived process on each tool call.
49634
+ Optionally, you can **deploy** the plugin as a **resident service**: one
49635
+ long-lived sandbox per plugin that serves every call. This is faster for
49636
+ frequent or heavy calls, but costs a running sandbox.
49637
+
49638
+ ## Precondition: tests must pass first
49639
+ Deploy ONLY after the plugin is validated AND its tests pass:
49640
+ 1. \`manage_plugin validate\` \u2014 no diagnostics.
49641
+ 2. \`manage_plugin create\`/\`update\` \u2014 written and reloaded.
49642
+ 3. \`manage_plugin test\` \u2014 every tool/connection test returns \`ok: true\`
49643
+ (a real \`connectionKey\` test for credentialed tools).
49644
+ Never deploy a plugin you have not tested: a resident service wraps the same
49645
+ handlers, so an untested handler fails identically \u2014 just in a service you now
49646
+ have to undeploy.
49647
+
49648
+ ## Choose a mode
49649
+ - \`"onDemand"\` (default) \u2014 the service is paused when idle and resumed on the
49650
+ next call. Best for plugins used occasionally. Lower steady cost.
49651
+ - \`"alwaysOn"\` \u2014 the service stays resident and is restarted if it becomes
49652
+ unhealthy. Best for plugins on a hot path or with expensive startup.
49653
+
49654
+ ## Deploy
49655
+ \`\`\`jsonc
49656
+ { "action": "deploy", "type": "my-plugin", "mode": "alwaysOn" }
49657
+ \`\`\`
49658
+ Returns \`{ deployed, status, endpoint, mode }\`. \`status\` is \`"running"\` when
49659
+ the service answered its health check. On failure you get
49660
+ \`{ deployed: false, error }\` \u2014 report it, do not treat it as success.
49661
+ Deploying again is idempotent: it reuses the same service.
49662
+
49663
+ ## Check status
49664
+ \`\`\`jsonc
49665
+ { "action": "status", "type": "my-plugin" }
49666
+ \`\`\`
49667
+ Returns \`{ status, endpoint?, mode?, lastHealthAt?, lastError? }\`. \`"absent"\`
49668
+ means no deployment exists; \`"failed"\` (with \`lastError\`) means the service is
49669
+ down. Inspect this before reporting the plugin as serving.
49670
+
49671
+ ## Undeploy
49672
+ \`\`\`jsonc
49673
+ { "action": "undeploy", "type": "my-plugin" }
49674
+ \`\`\`
49675
+ Returns \`{ undeployed }\`. Undeploy removes the resident sandbox and its
49676
+ deployment record; the plugin files and manifest are untouched, so tool calls
49677
+ fall back to the short-process path. Use it to stop paying for a service you no
49678
+ longer need.
49679
+
49680
+ ## When resident services are unavailable
49681
+ If the environment has no deployment service registered, deploy/undeploy/status
49682
+ return a diagnostic (\`{ deployed: false, error }\` etc.) instead of throwing.
49683
+ That is not a plugin defect: the plugin still works through the default
49684
+ short-process path, so report it as not-deployed rather than broken.
48926
49685
  `,
48927
49686
  resources: {}
48928
49687
  }
@@ -48959,9 +49718,197 @@ the connector and give you its key. Only after a real \`connectionKey\` test
48959
49718
  passes may you call the tool verified; without it, report functionality as
48960
49719
  unverified (missing credentials), not as passing. Use \`shell_exec\` (the tool
48961
49720
  provided by the \`code_eval\` middleware) to run \`node handlers/x.js\` for manual
48962
- checks when reproducing a failure.`;
49721
+ checks when reproducing a failure.
49722
+
49723
+ After the plugin passes \`validate\` and its tests, deploy it as a resident
49724
+ service: load "sandbox-plugins-deploy" and follow it. Choose \`onDemand\`
49725
+ (default) or \`alwaysOn\` for hot paths, call \`manage_plugin deploy\` with the
49726
+ chosen \`mode\`, then confirm with \`manage_plugin status\` (\`running\`). Only claim
49727
+ the plugin is deployed and serving once \`status\` reports \`running\`; if the
49728
+ environment reports no deployment service, say so and treat deployment as
49729
+ unavailable \u2014 do NOT claim success. \`undeploy\` removes the service without
49730
+ touching the plugin files.`;
49731
+
49732
+ // src/middlewares/sandboxPluginsUi.ts
49733
+ var SANDBOX_PLUGINS_UI_RESOURCE = "ui://sandbox-plugins/app";
49734
+ var SANDBOX_PLUGINS_APP_HTML = `<!doctype html>
49735
+ <html lang="en">
49736
+ <head>
49737
+ <meta charset="utf-8" />
49738
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
49739
+ <title>Plugin Deployments</title>
49740
+ <style>
49741
+ :root { color-scheme: light dark; }
49742
+ * { box-sizing: border-box; }
49743
+ body {
49744
+ margin: 0;
49745
+ padding: 14px 16px;
49746
+ font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
49747
+ font-size: 13px;
49748
+ color: #1f2430;
49749
+ background: transparent;
49750
+ }
49751
+ h1 { font-size: 14px; font-weight: 600; margin: 0 0 2px; }
49752
+ .subtitle { color: #6b7280; font-size: 12px; margin: 0 0 12px; }
49753
+ table { width: 100%; border-collapse: collapse; }
49754
+ th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid #eceef2; vertical-align: top; }
49755
+ th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280; font-weight: 600; }
49756
+ td.type { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; white-space: nowrap; }
49757
+ td.mode { white-space: nowrap; color: #4b5563; }
49758
+ td.detail { color: #4b5563; word-break: break-word; max-width: 280px; }
49759
+ .muted { color: #9ca3af; }
49760
+ .status { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
49761
+ .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: #9ca3af; }
49762
+ .dot.running { background: #22c55e; }
49763
+ .dot.failed { background: #ef4444; }
49764
+ .dot.requested, .dot.provisioning, .dot.stopping { background: #f59e0b; }
49765
+ .dot.paused, .dot.stopped { background: #94a3b8; }
49766
+ .state { color: #6b7280; padding: 14px 2px; }
49767
+ .error { margin: 0 0 10px; padding: 8px 10px; border-radius: 8px; background: #fef2f2; color: #b91c1c; }
49768
+ </style>
49769
+ </head>
49770
+ <body>
49771
+ <h1>Plugin Deployments</h1>
49772
+ <p class="subtitle">Resident services deployed for this tenant. Read-only.</p>
49773
+ <div id="root"><p class="state">Loading\u2026</p></div>
49774
+ <script>
49775
+ (function () {
49776
+ var root = document.getElementById("root");
49777
+
49778
+ function post(message) {
49779
+ window.parent.postMessage(message, "*");
49780
+ }
49781
+ function notify(method, params) {
49782
+ post({ jsonrpc: "2.0", method: method, params: params });
49783
+ }
49784
+ var nextId = 1;
49785
+ function rpc(method, params) {
49786
+ var id = nextId++;
49787
+ return new Promise(function (resolve, reject) {
49788
+ function onMessage(event) {
49789
+ var data = event.data;
49790
+ if (!data || data.id !== id) return;
49791
+ window.removeEventListener("message", onMessage);
49792
+ if (data.error) reject(new Error((data.error && data.error.message) || "rpc error"));
49793
+ else resolve(data.result);
49794
+ }
49795
+ window.addEventListener("message", onMessage);
49796
+ post({ jsonrpc: "2.0", id: id, method: method, params: params });
49797
+ });
49798
+ }
49799
+
49800
+ function sendSize() {
49801
+ var width = Math.ceil(document.documentElement.scrollWidth || document.body.scrollWidth);
49802
+ var height = Math.ceil(document.documentElement.scrollHeight || document.body.scrollHeight);
49803
+ notify("ui/notifications/size-changed", { width: width, height: height });
49804
+ }
49805
+
49806
+ function esc(value) {
49807
+ return String(value === null || value === undefined ? "" : value).replace(/[&<>"']/g, function (ch) {
49808
+ return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[ch];
49809
+ });
49810
+ }
49811
+
49812
+ function statusClass(status) {
49813
+ switch (status) {
49814
+ case "running": return "running";
49815
+ case "failed": return "failed";
49816
+ case "requested":
49817
+ case "provisioning":
49818
+ case "stopping": return "requested";
49819
+ case "paused":
49820
+ case "stopped": return "paused";
49821
+ default: return "";
49822
+ }
49823
+ }
49824
+
49825
+ function healthCell(item) {
49826
+ if (item.lastError) return '<span style="color:#b91c1c">' + esc(item.lastError) + "</span>";
49827
+ if (item.lastHealthAt) return '<span class="muted">' + esc(item.lastHealthAt) + "</span>";
49828
+ return '<span class="muted">\u2014</span>';
49829
+ }
49830
+
49831
+ function render(payload) {
49832
+ var deployments = payload && Array.isArray(payload.deployments) ? payload.deployments : [];
49833
+ var error = payload && payload.error ? String(payload.error) : "";
49834
+ var html = "";
49835
+ if (error) html += '<div class="error">' + esc(error) + "</div>";
49836
+ if (deployments.length === 0) {
49837
+ html += '<p class="state">' + (error ? "No deployments available." : "No resident plugin deployments.") + "</p>";
49838
+ root.innerHTML = html;
49839
+ sendSize();
49840
+ return;
49841
+ }
49842
+ html += "<table><thead><tr><th>Plugin</th><th>Status</th><th>Mode</th><th>Endpoint</th><th>Health</th></tr></thead><tbody>";
49843
+ deployments.forEach(function (item) {
49844
+ var status = item && item.status ? String(item.status) : "unknown";
49845
+ html += "<tr>";
49846
+ html += '<td class="type">' + esc(item && item.pluginType) + "</td>";
49847
+ html += '<td><span class="status"><span class="dot ' + statusClass(status) + '"></span>' + esc(status) + "</span></td>";
49848
+ html += '<td class="mode">' + esc(item && item.mode) + "</td>";
49849
+ html += '<td class="detail">' + (item && item.endpoint ? esc(item.endpoint) : '<span class="muted">\u2014</span>') + "</td>";
49850
+ html += '<td class="detail">' + healthCell(item || {}) + "</td>";
49851
+ html += "</tr>";
49852
+ });
49853
+ html += "</tbody></table>";
49854
+ root.innerHTML = html;
49855
+ sendSize();
49856
+ }
49857
+
49858
+ function readResult(params) {
49859
+ if (!params || typeof params !== "object") return null;
49860
+ if (params.structuredContent && typeof params.structuredContent === "object") {
49861
+ return params.structuredContent;
49862
+ }
49863
+ var content = params.content;
49864
+ var text = content && content[0] && typeof content[0].text === "string" ? content[0].text : "";
49865
+ if (!text) return null;
49866
+ try { return JSON.parse(text); } catch (err) { return null; }
49867
+ }
49868
+
49869
+ // 1) Register the tool-result listener BEFORE initializing so the data
49870
+ // the host pushes right after the handshake is never missed.
49871
+ window.addEventListener("message", function (event) {
49872
+ var data = event.data;
49873
+ if (!data || typeof data !== "object" || typeof data.method !== "string") return;
49874
+ if (data.method === "ui/notifications/tool-result") {
49875
+ render(readResult(data.params));
49876
+ }
49877
+ });
49878
+
49879
+ if (typeof ResizeObserver !== "undefined") {
49880
+ new ResizeObserver(sendSize).observe(document.documentElement);
49881
+ } else {
49882
+ window.addEventListener("resize", sendSize);
49883
+ }
49884
+
49885
+ // 2) MCP Apps lifecycle: initialize, then signal readiness. The host
49886
+ // pushes tool data only after this completes.
49887
+ rpc("ui/initialize", {
49888
+ protocolVersion: "2025-06-18",
49889
+ capabilities: {},
49890
+ clientInfo: { name: "sandbox-plugins-ui", version: "1.0.0" },
49891
+ appCapabilities: { availableDisplayModes: ["inline"] }
49892
+ }).then(function () {
49893
+ notify("ui/notifications/initialized", {});
49894
+ sendSize();
49895
+ }).catch(function (err) {
49896
+ root.innerHTML = '<p class="state">Failed to initialize: ' + esc((err && err.message) || err) + "</p>";
49897
+ sendSize();
49898
+ });
49899
+ })();
49900
+ </script>
49901
+ </body>
49902
+ </html>
49903
+ `;
48963
49904
 
48964
49905
  // src/middlewares/sandboxPluginsMiddleware.ts
49906
+ var SANDBOX_PLUGINS_UI_REF = {
49907
+ kind: "plugin",
49908
+ pluginType: "sandbox-plugins",
49909
+ resource: SANDBOX_PLUGINS_UI_RESOURCE,
49910
+ displayMode: "inline"
49911
+ };
48965
49912
  var PLUGIN_BUILDER_SKILLS = [...Object.keys(SANDBOX_PLUGIN_SKILLS), "task-tracking"];
48966
49913
  function tenantIdFrom(exeConfig) {
48967
49914
  const c = exeConfig;
@@ -48979,6 +49926,15 @@ function collectSecretValues(connection, fields) {
48979
49926
  function errorText(err) {
48980
49927
  return err instanceof Error ? err.message : String(err);
48981
49928
  }
49929
+ function deploymentUnavailable(action) {
49930
+ const error = `plugin deployment service is not configured; cannot ${action}. Resident plugin services are unavailable in this environment.`;
49931
+ if (action === "undeploy") return JSON.stringify({ undeployed: false, error });
49932
+ if (action === "status") return JSON.stringify({ status: "unavailable", error });
49933
+ return JSON.stringify({ deployed: false, error });
49934
+ }
49935
+ function deploymentsResult(payload) {
49936
+ return appendUiFence(JSON.stringify(payload), SANDBOX_PLUGINS_UI_REF);
49937
+ }
48982
49938
  function redactString(value, secrets) {
48983
49939
  let out = value;
48984
49940
  for (const secret of secrets) out = out.split(secret).join("[redacted]");
@@ -48999,10 +49955,24 @@ function redactSecretValues(payload, secrets) {
48999
49955
  return walk(payload);
49000
49956
  }
49001
49957
  var managePluginSchema = import_zod71.z.object({
49002
- action: import_zod71.z.enum(["list", "get", "validate", "create", "update", "delete", "reload", "test"]),
49958
+ action: import_zod71.z.enum([
49959
+ "list",
49960
+ "get",
49961
+ "validate",
49962
+ "create",
49963
+ "update",
49964
+ "delete",
49965
+ "reload",
49966
+ "test",
49967
+ "deploy",
49968
+ "undeploy",
49969
+ "status",
49970
+ "deployments"
49971
+ ]),
49003
49972
  type: import_zod71.z.string().optional(),
49004
49973
  manifest: import_zod71.z.record(import_zod71.z.unknown()).optional(),
49005
49974
  handlers: import_zod71.z.record(import_zod71.z.string()).optional(),
49975
+ mode: import_zod71.z.enum(["onDemand", "alwaysOn"]).optional(),
49006
49976
  target: import_zod71.z.object({
49007
49977
  kind: import_zod71.z.enum(["tool", "connection-test", "connection-discover"]),
49008
49978
  name: import_zod71.z.string().optional(),
@@ -49015,9 +49985,18 @@ var sandboxPluginsPlugin = {
49015
49985
  type: "sandbox-plugins",
49016
49986
  category: "execution",
49017
49987
  name: "Sandbox Plugins",
49018
- description: "Author and manage tenant plugins stored in the sandbox: list/validate/create/update/delete/reload/test. Tools are agent-callable JS handlers and may declare an optional interactive HTML UI (rendered as a sandboxed MCP App). Pairs with the Skill plugin for authoring guidance.",
49988
+ description: "Author and manage tenant plugins stored in the sandbox: list/validate/create/update/delete/reload/test, and deploy/undeploy/status/deployments to run a plugin as a resident service. Tools are agent-callable JS handlers and may declare an optional interactive HTML UI (rendered as a sandboxed MCP App). Pairs with the Skill plugin for authoring guidance.",
49019
49989
  recommends: ["skill"],
49020
- tools: [{ name: "manage_plugin", description: "Author and manage tenant sandbox plugins (tools, optional per-tool HTML UI, connections, and Open/MCP exposure)" }],
49990
+ tools: [
49991
+ {
49992
+ name: "manage_plugin",
49993
+ description: "Author and manage tenant sandbox plugins (tools, optional per-tool HTML UI, connections, Open/MCP exposure, and resident deployment). Actions: list/get/validate/create/update/delete/reload/test, deploy/undeploy/status, and `deployments` to list every resident deployment for the tenant.",
49994
+ ui: { resource: SANDBOX_PLUGINS_UI_RESOURCE, displayMode: "inline" }
49995
+ }
49996
+ ],
49997
+ uiResources: {
49998
+ [SANDBOX_PLUGINS_UI_RESOURCE]: { html: SANDBOX_PLUGINS_APP_HTML }
49999
+ },
49021
50000
  configSchema: { type: "object", properties: {} },
49022
50001
  defaultConfig: {}
49023
50002
  },
@@ -49038,7 +50017,7 @@ var sandboxPluginsPlugin = {
49038
50017
  id: "sandbox-plugins",
49039
50018
  type: "sandbox-plugins",
49040
50019
  name: "Sandbox Plugins",
49041
- description: "Author and manage tenant plugins (list/validate/create/update/delete/reload/test)",
50020
+ description: "Author and manage tenant plugins (list/validate/create/update/delete/reload/test/deploy/undeploy/status)",
49042
50021
  enabled: true,
49043
50022
  config: {}
49044
50023
  },
@@ -49234,13 +50213,74 @@ var sandboxPluginsPlugin = {
49234
50213
  return JSON.stringify(redactSecretValues({ ok: false, error: errorText(err) }, secrets));
49235
50214
  }
49236
50215
  }
50216
+ case "deploy": {
50217
+ if (!input.type) return "type is required";
50218
+ const service3 = getPluginDeploymentService();
50219
+ if (!service3) return deploymentUnavailable("deploy");
50220
+ try {
50221
+ const result = await service3.deploy(tenantId2, input.type, {
50222
+ ...input.mode === void 0 ? {} : { mode: input.mode }
50223
+ });
50224
+ return JSON.stringify({
50225
+ deployed: true,
50226
+ status: result.status,
50227
+ endpoint: result.endpoint,
50228
+ mode: result.mode
50229
+ });
50230
+ } catch (err) {
50231
+ return JSON.stringify({ deployed: false, error: errorText(err) });
50232
+ }
50233
+ }
50234
+ case "undeploy": {
50235
+ if (!input.type) return "type is required";
50236
+ const service3 = getPluginDeploymentService();
50237
+ if (!service3) return deploymentUnavailable("undeploy");
50238
+ try {
50239
+ const result = await service3.undeploy(tenantId2, input.type);
50240
+ return JSON.stringify({ undeployed: result.undeployed });
50241
+ } catch (err) {
50242
+ return JSON.stringify({ undeployed: false, error: errorText(err) });
50243
+ }
50244
+ }
50245
+ case "status": {
50246
+ if (!input.type) return "type is required";
50247
+ const service3 = getPluginDeploymentService();
50248
+ if (!service3) return deploymentUnavailable("status");
50249
+ try {
50250
+ const result = await service3.status(tenantId2, input.type);
50251
+ return JSON.stringify({
50252
+ status: result.status,
50253
+ endpoint: result.endpoint,
50254
+ mode: result.mode,
50255
+ lastHealthAt: result.lastHealthAt,
50256
+ lastError: result.lastError
50257
+ });
50258
+ } catch (err) {
50259
+ return JSON.stringify({ status: "unavailable", error: errorText(err) });
50260
+ }
50261
+ }
50262
+ case "deployments": {
50263
+ const service3 = getPluginDeploymentService();
50264
+ if (!service3) {
50265
+ return deploymentsResult({
50266
+ deployments: [],
50267
+ error: "plugin deployment service is not configured; cannot list deployments. Resident plugin services are unavailable in this environment."
50268
+ });
50269
+ }
50270
+ try {
50271
+ const deployments = await service3.list(tenantId2);
50272
+ return deploymentsResult({ deployments });
50273
+ } catch (err) {
50274
+ return deploymentsResult({ deployments: [], error: errorText(err) });
50275
+ }
50276
+ }
49237
50277
  default:
49238
50278
  return `unsupported action: ${input.action}`;
49239
50279
  }
49240
50280
  },
49241
50281
  {
49242
50282
  name: "manage_plugin",
49243
- description: "Author and manage tenant sandbox plugins (list/get/validate/create/update/delete/reload/test).",
50283
+ description: "Author and manage tenant sandbox plugins (list/get/validate/create/update/delete/reload/test) and deploy/undeploy/status a plugin as a resident service.",
49244
50284
  schema: managePluginSchema
49245
50285
  }
49246
50286
  )
@@ -49295,6 +50335,526 @@ function buildBundleFromDist(distDir, version) {
49295
50335
  };
49296
50336
  }
49297
50337
 
50338
+ // src/sandbox_plugin/PluginDeploymentRuntimeRegistry.ts
50339
+ var PluginDeploymentRuntimeRegistry = class {
50340
+ /**
50341
+ * @param options - Optional injectable clock for deterministic health timestamps.
50342
+ */
50343
+ constructor(options = {}) {
50344
+ this.entries = /* @__PURE__ */ new Map();
50345
+ this.inFlight = /* @__PURE__ */ new Map();
50346
+ this.now = options.now ?? Date.now;
50347
+ }
50348
+ /**
50349
+ * Cache the resolved proxy endpoint for a deployment.
50350
+ *
50351
+ * @param id - Composed key `${tenantId}:${pluginType}`.
50352
+ * @param endpoint - Relative proxy path, e.g. `/api/sandboxes/plugin-abc/proxy/8080`.
50353
+ */
50354
+ setEndpoint(id, endpoint) {
50355
+ this.entry(id).endpoint = endpoint;
50356
+ }
50357
+ /**
50358
+ * Read the cached endpoint for a deployment.
50359
+ *
50360
+ * @param id - Composed key `${tenantId}:${pluginType}`.
50361
+ * @returns The cached endpoint, or `undefined` when none is known.
50362
+ */
50363
+ getEndpoint(id) {
50364
+ return this.entries.get(id)?.endpoint;
50365
+ }
50366
+ /**
50367
+ * Record a successful health observation and clear any prior error.
50368
+ *
50369
+ * @param id - Composed key `${tenantId}:${pluginType}`.
50370
+ */
50371
+ markHealthy(id) {
50372
+ const entry = this.entry(id);
50373
+ entry.lastHealthAt = this.now();
50374
+ delete entry.lastError;
50375
+ }
50376
+ /**
50377
+ * Record a failed health observation.
50378
+ *
50379
+ * @param id - Composed key `${tenantId}:${pluginType}`.
50380
+ * @param error - Human-readable failure description.
50381
+ */
50382
+ markUnhealthy(id, error) {
50383
+ const entry = this.entry(id);
50384
+ entry.lastHealthAt = this.now();
50385
+ entry.lastError = error;
50386
+ }
50387
+ /**
50388
+ * Read the last observed health for a deployment.
50389
+ *
50390
+ * @param id - Composed key `${tenantId}:${pluginType}`.
50391
+ * @returns The cached health, or `undefined` when the id is unknown.
50392
+ */
50393
+ getHealth(id) {
50394
+ const entry = this.entries.get(id);
50395
+ if (!entry) return void 0;
50396
+ const health = {};
50397
+ if (entry.lastHealthAt !== void 0) health.lastHealthAt = entry.lastHealthAt;
50398
+ if (entry.lastError !== void 0) health.lastError = entry.lastError;
50399
+ return health;
50400
+ }
50401
+ /**
50402
+ * Run a cold start/resume factory at most once per id at a time.
50403
+ *
50404
+ * Concurrent calls for the same id return the same in-flight promise and run
50405
+ * `factory` exactly once. The in-flight entry is cleared when the factory
50406
+ * settles, so a later call runs it again (retry after failure is allowed).
50407
+ *
50408
+ * @param id - Composed key `${tenantId}:${pluginType}`.
50409
+ * @param factory - Idempotent start/resume operation for the resident service.
50410
+ * @returns A promise that settles with the factory outcome.
50411
+ */
50412
+ ensureRunning(id, factory) {
50413
+ const existing = this.inFlight.get(id);
50414
+ if (existing) return existing;
50415
+ let started;
50416
+ try {
50417
+ started = factory();
50418
+ } catch (error) {
50419
+ started = Promise.reject(error);
50420
+ }
50421
+ const tracked = started.finally(() => {
50422
+ if (this.inFlight.get(id) === tracked) {
50423
+ this.inFlight.delete(id);
50424
+ }
50425
+ });
50426
+ this.inFlight.set(id, tracked);
50427
+ return tracked;
50428
+ }
50429
+ /**
50430
+ * Drop cached endpoint and health for one deployment.
50431
+ *
50432
+ * @param id - Composed key `${tenantId}:${pluginType}`.
50433
+ */
50434
+ clear(id) {
50435
+ this.entries.delete(id);
50436
+ }
50437
+ /** Drop all cached endpoints, health, and in-flight coordination entries. */
50438
+ reset() {
50439
+ this.entries.clear();
50440
+ this.inFlight.clear();
50441
+ }
50442
+ entry(id) {
50443
+ const existing = this.entries.get(id);
50444
+ if (existing) return existing;
50445
+ const created = {};
50446
+ this.entries.set(id, created);
50447
+ return created;
50448
+ }
50449
+ };
50450
+
50451
+ // src/sandbox_plugin/host/pluginHostProtocol.ts
50452
+ var INLINE_INPUT_LIMIT_BYTES2 = 48 * 1024;
50453
+ var DEFAULT_HANDLER_TIMEOUT_MS = 6e4;
50454
+ var MIN_HANDLER_TIMEOUT_MS = 1e3;
50455
+ var MAX_HANDLER_TIMEOUT_MS = 3e5;
50456
+ function isRecord11(value) {
50457
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50458
+ }
50459
+ function isEnvelope3(value) {
50460
+ return isRecord11(value) && typeof value.ok === "boolean";
50461
+ }
50462
+ function parseHostArgs(argv) {
50463
+ let type;
50464
+ let port;
50465
+ for (let index = 0; index < argv.length; index += 1) {
50466
+ const token = argv[index];
50467
+ if (token === "--type" && index + 1 < argv.length) {
50468
+ type = argv[index + 1];
50469
+ index += 1;
50470
+ } else if (token.startsWith("--type=")) {
50471
+ type = token.slice("--type=".length);
50472
+ } else if (token === "--port" && index + 1 < argv.length) {
50473
+ port = argv[index + 1];
50474
+ index += 1;
50475
+ } else if (token.startsWith("--port=")) {
50476
+ port = token.slice("--port=".length);
50477
+ }
50478
+ }
50479
+ const parsedPort = Number(port);
50480
+ if (typeof type !== "string" || type.length === 0) {
50481
+ throw new Error("parseHostArgs: missing or empty --type");
50482
+ }
50483
+ if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
50484
+ throw new Error("parseHostArgs: invalid --port");
50485
+ }
50486
+ return { type, port: parsedPort };
50487
+ }
50488
+ function resolveHandlerTimeoutMs(value) {
50489
+ if (value === null || value === void 0 || value === "") return DEFAULT_HANDLER_TIMEOUT_MS;
50490
+ const numeric = typeof value === "number" ? value : Number(value);
50491
+ if (!Number.isFinite(numeric)) return DEFAULT_HANDLER_TIMEOUT_MS;
50492
+ return Math.min(Math.max(Math.round(numeric), MIN_HANDLER_TIMEOUT_MS), MAX_HANDLER_TIMEOUT_MS);
50493
+ }
50494
+ function parseHandlerEnvelope(stdout) {
50495
+ const trimmed = stdout.trim();
50496
+ if (!trimmed) return { ok: false, error: "empty handler output" };
50497
+ const whole = tryParse(trimmed);
50498
+ if (isEnvelope3(whole)) return normalizeEnvelope(whole);
50499
+ const lines = stdout.split("\n");
50500
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
50501
+ const line = lines[index]?.trim();
50502
+ if (!line) continue;
50503
+ const candidate = tryParse(line);
50504
+ if (isEnvelope3(candidate)) return normalizeEnvelope(candidate);
50505
+ }
50506
+ return { ok: true, result: trimmed };
50507
+ }
50508
+ function buildHandlerCommand(handlerPath) {
50509
+ return `node ${quotePosixShellArg2(handlerPath)}`;
50510
+ }
50511
+ function encodeInput(input) {
50512
+ return Buffer.from(JSON.stringify(input ?? null), "utf8").toString("base64");
50513
+ }
50514
+ function buildConnectionPayload2(args) {
50515
+ if (Array.isArray(args.connections) && args.connections.length > 0) {
50516
+ return {
50517
+ connection: args.connection ?? args.connections[0]?.config,
50518
+ connections: args.connections
50519
+ };
50520
+ }
50521
+ if (args.connection) {
50522
+ return { connection: args.connection };
50523
+ }
50524
+ return null;
50525
+ }
50526
+ function quotePosixShellArg2(value) {
50527
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
50528
+ }
50529
+ function tryParse(text) {
50530
+ try {
50531
+ return JSON.parse(text);
50532
+ } catch {
50533
+ return void 0;
50534
+ }
50535
+ }
50536
+ function normalizeEnvelope(parsed) {
50537
+ if (parsed.ok) return { ok: true, result: parsed.result };
50538
+ return { ok: false, error: typeof parsed.error === "string" ? parsed.error : void 0 };
50539
+ }
50540
+
50541
+ // src/sandbox_plugin/host/pluginHostSource.ts
50542
+ var import_node_crypto15 = require("crypto");
50543
+ var PLUGIN_HOST_VERSION = "1.1.0";
50544
+ var PLUGIN_HOST_SERVER_SOURCE = String.raw`#!/usr/bin/env node
50545
+ "use strict";
50546
+
50547
+ var http = require("node:http");
50548
+ var fs = require("node:fs");
50549
+ var os = require("node:os");
50550
+ var path = require("node:path");
50551
+ var crypto = require("node:crypto");
50552
+ var execFile = require("node:child_process").execFile;
50553
+
50554
+ var VERSION = "${PLUGIN_HOST_VERSION}";
50555
+ var PLUGINS_ROOT = "/root/.agents/plugins";
50556
+ var INLINE_INPUT_LIMIT_BYTES = 48 * 1024;
50557
+ var MAX_BODY_BYTES = 8 * 1024 * 1024;
50558
+ var MAX_RESULT_BYTES = 16 * 1024 * 1024;
50559
+ var DEFAULT_HANDLER_TIMEOUT_MS = 60000;
50560
+ var MIN_HANDLER_TIMEOUT_MS = 1000;
50561
+ var MAX_HANDLER_TIMEOUT_MS = 300000;
50562
+
50563
+ function resolveHandlerTimeoutMs(value) {
50564
+ if (value === null || value === undefined || value === "") {
50565
+ return DEFAULT_HANDLER_TIMEOUT_MS;
50566
+ }
50567
+ var numeric = typeof value === "number" ? value : Number(value);
50568
+ if (!isFinite(numeric)) {
50569
+ return DEFAULT_HANDLER_TIMEOUT_MS;
50570
+ }
50571
+ return Math.min(Math.max(Math.round(numeric), MIN_HANDLER_TIMEOUT_MS), MAX_HANDLER_TIMEOUT_MS);
50572
+ }
50573
+
50574
+ function isRecord(value) {
50575
+ return typeof value === "object" && value !== null && !Array.isArray(value);
50576
+ }
50577
+
50578
+ function isEnvelope(value) {
50579
+ return isRecord(value) && typeof value.ok === "boolean";
50580
+ }
50581
+
50582
+ function tryParse(text) {
50583
+ try {
50584
+ return JSON.parse(text);
50585
+ } catch (err) {
50586
+ return undefined;
50587
+ }
50588
+ }
50589
+
50590
+ function normalizeEnvelope(parsed) {
50591
+ if (parsed.ok) {
50592
+ return { ok: true, result: parsed.result };
50593
+ }
50594
+ return { ok: false, error: typeof parsed.error === "string" ? parsed.error : undefined };
50595
+ }
50596
+
50597
+ function parseHandlerEnvelope(stdout) {
50598
+ var text = String(stdout == null ? "" : stdout);
50599
+ var trimmed = text.trim();
50600
+ if (!trimmed) {
50601
+ // Deliberate divergence from SandboxPluginExecutor, which returns
50602
+ // { ok: true, result: "" } for empty stdout; the resident host treats empty
50603
+ // output as a handler failure so a crashed/no-op handler is not a "success".
50604
+ return { ok: false, error: "empty handler output" };
50605
+ }
50606
+ var whole = tryParse(trimmed);
50607
+ if (isEnvelope(whole)) {
50608
+ return normalizeEnvelope(whole);
50609
+ }
50610
+ var lines = text.split("\n");
50611
+ for (var index = lines.length - 1; index >= 0; index -= 1) {
50612
+ var line = lines[index].trim();
50613
+ if (!line) {
50614
+ continue;
50615
+ }
50616
+ var candidate = tryParse(line);
50617
+ if (isEnvelope(candidate)) {
50618
+ return normalizeEnvelope(candidate);
50619
+ }
50620
+ }
50621
+ return { ok: true, result: trimmed };
50622
+ }
50623
+
50624
+ function quotePosixShellArg(value) {
50625
+ return "'" + String(value).replace(/'/g, "'\"'\"'") + "'";
50626
+ }
50627
+
50628
+ function buildHandlerCommand(handlerPath) {
50629
+ return "node " + quotePosixShellArg(handlerPath);
50630
+ }
50631
+
50632
+ function parseHostArgs(argv) {
50633
+ var type;
50634
+ var port;
50635
+ for (var index = 0; index < argv.length; index += 1) {
50636
+ var token = argv[index];
50637
+ if (token === "--type" && index + 1 < argv.length) {
50638
+ type = argv[index + 1];
50639
+ index += 1;
50640
+ } else if (token.indexOf("--type=") === 0) {
50641
+ type = token.slice("--type=".length);
50642
+ } else if (token === "--port" && index + 1 < argv.length) {
50643
+ port = argv[index + 1];
50644
+ index += 1;
50645
+ } else if (token.indexOf("--port=") === 0) {
50646
+ port = token.slice("--port=".length);
50647
+ }
50648
+ }
50649
+ var parsedPort = Number(port);
50650
+ if (typeof type !== "string" || type.length === 0) {
50651
+ throw new Error("parseHostArgs: missing or empty --type");
50652
+ }
50653
+ if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
50654
+ throw new Error("parseHostArgs: invalid --port");
50655
+ }
50656
+ return { type: type, port: parsedPort };
50657
+ }
50658
+
50659
+ function encodeInput(input) {
50660
+ return Buffer.from(JSON.stringify(input === undefined ? null : input), "utf8").toString("base64");
50661
+ }
50662
+
50663
+ function buildConnectionPayload(body) {
50664
+ if (Array.isArray(body.connections) && body.connections.length > 0) {
50665
+ return {
50666
+ connection: body.connection !== undefined ? body.connection : body.connections[0].config,
50667
+ connections: body.connections,
50668
+ };
50669
+ }
50670
+ if (body.connection) {
50671
+ return { connection: body.connection };
50672
+ }
50673
+ return null;
50674
+ }
50675
+
50676
+ function resolveHandlerPath(type, handler) {
50677
+ if (typeof handler !== "string" || handler.length === 0) {
50678
+ throw new Error("missing handler");
50679
+ }
50680
+ if (handler.charAt(0) === "/" || handler.split("/").indexOf("..") !== -1) {
50681
+ throw new Error("invalid handler path");
50682
+ }
50683
+ return path.join(PLUGINS_ROOT, type, handler);
50684
+ }
50685
+
50686
+ function readJsonBody(req) {
50687
+ return new Promise(function (resolve, reject) {
50688
+ var chunks = [];
50689
+ var size = 0;
50690
+ req.on("data", function (chunk) {
50691
+ size += chunk.length;
50692
+ if (size > MAX_BODY_BYTES) {
50693
+ reject(new Error("request body too large"));
50694
+ req.destroy();
50695
+ return;
50696
+ }
50697
+ chunks.push(chunk);
50698
+ });
50699
+ req.on("end", function () {
50700
+ try {
50701
+ var text = Buffer.concat(chunks).toString("utf8");
50702
+ resolve(text ? JSON.parse(text) : {});
50703
+ } catch (err) {
50704
+ reject(new Error("invalid JSON body"));
50705
+ }
50706
+ });
50707
+ req.on("error", reject);
50708
+ });
50709
+ }
50710
+
50711
+ function runNode(handlerPath, pluginDir, env, timeoutMs) {
50712
+ return new Promise(function (resolve) {
50713
+ execFile(
50714
+ process.execPath,
50715
+ [handlerPath],
50716
+ { cwd: pluginDir, env: env, timeout: timeoutMs, maxBuffer: MAX_RESULT_BYTES },
50717
+ function (error, stdout, stderr) {
50718
+ resolve({ error: error, stdout: stdout || "", stderr: stderr || "" });
50719
+ }
50720
+ );
50721
+ });
50722
+ }
50723
+
50724
+ function failureFromExec(exec, command) {
50725
+ var parsed = parseHandlerEnvelope(exec.stdout);
50726
+ if (!parsed.ok) {
50727
+ return parsed;
50728
+ }
50729
+ var code = exec.error && typeof exec.error.code === "number" ? exec.error.code : "n/a";
50730
+ var detail = exec.stdout ? exec.stdout.slice(0, 4096) : exec.error ? String(exec.error.message) : "";
50731
+ return { ok: false, error: "handler failed (exit " + code + "): " + detail + " [" + command + "]" };
50732
+ }
50733
+
50734
+ async function runHandler(body) {
50735
+ var pluginDir = path.join(PLUGINS_ROOT, config.type);
50736
+ var handlerPath = resolveHandlerPath(config.type, body.handler);
50737
+ var command = buildHandlerCommand(handlerPath);
50738
+ var inputJson = JSON.stringify(body.input === undefined ? null : body.input);
50739
+ var connectionPayload = buildConnectionPayload(body);
50740
+ var inputIsLarge = Buffer.byteLength(inputJson, "utf8") > INLINE_INPUT_LIMIT_BYTES;
50741
+ var needsTempDir = inputIsLarge || connectionPayload !== null;
50742
+ var tempDir = null;
50743
+ var env = Object.assign({}, process.env);
50744
+ try {
50745
+ if (needsTempDir) {
50746
+ tempDir = path.join(os.tmpdir(), "plugin-run-" + Date.now() + "-" + Math.random().toString(36).slice(2));
50747
+ fs.mkdirSync(tempDir, { recursive: true });
50748
+ }
50749
+ if (inputIsLarge) {
50750
+ var inputFile = path.join(tempDir, "input.json");
50751
+ fs.writeFileSync(inputFile, inputJson);
50752
+ env.PLUGIN_INPUT_FILE = inputFile;
50753
+ } else {
50754
+ env.PLUGIN_INPUT = encodeInput(body.input);
50755
+ }
50756
+ if (connectionPayload !== null) {
50757
+ var connectionFile = path.join(tempDir, "connection.json");
50758
+ fs.writeFileSync(connectionFile, JSON.stringify(connectionPayload));
50759
+ env.PLUGIN_CONNECTION_FILE = connectionFile;
50760
+ }
50761
+ env.PLUGIN_CONTEXT = encodeInput(body.context || {});
50762
+ var handlerTimeoutMs = resolveHandlerTimeoutMs(body.timeoutMs);
50763
+ var exec = await runNode(handlerPath, pluginDir, env, handlerTimeoutMs);
50764
+ if (exec.error) {
50765
+ return failureFromExec(exec, command);
50766
+ }
50767
+ return parseHandlerEnvelope(exec.stdout);
50768
+ } finally {
50769
+ if (tempDir) {
50770
+ try {
50771
+ fs.rmSync(tempDir, { recursive: true, force: true });
50772
+ } catch (err) {
50773
+ // best-effort cleanup
50774
+ }
50775
+ }
50776
+ }
50777
+ }
50778
+
50779
+ function manifestHash() {
50780
+ try {
50781
+ var content = fs.readFileSync(path.join(PLUGINS_ROOT, config.type, "plugin.json"), "utf8");
50782
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
50783
+ } catch (err) {
50784
+ return "";
50785
+ }
50786
+ }
50787
+
50788
+ function sendJson(res, status, payload) {
50789
+ var body = JSON.stringify(payload);
50790
+ res.writeHead(status, {
50791
+ "content-type": "application/json",
50792
+ "content-length": Buffer.byteLength(body),
50793
+ });
50794
+ res.end(body);
50795
+ }
50796
+
50797
+ function handleRequest(req, res) {
50798
+ var url = String(req.url || "").split("?")[0];
50799
+ if (req.method === "GET" && url === "/health") {
50800
+ sendJson(res, 200, { ok: true, version: VERSION });
50801
+ return;
50802
+ }
50803
+ if (req.method === "GET" && url === "/meta") {
50804
+ sendJson(res, 200, { type: config.type, manifestHash: manifestHash() });
50805
+ return;
50806
+ }
50807
+ if (req.method === "POST" && url === "/invoke") {
50808
+ readJsonBody(req)
50809
+ .then(function (body) {
50810
+ if (!isRecord(body)) {
50811
+ sendJson(res, 400, { ok: false, error: "invalid body" });
50812
+ return;
50813
+ }
50814
+ return runHandler(body).then(function (result) {
50815
+ sendJson(res, 200, result);
50816
+ });
50817
+ })
50818
+ .catch(function (err) {
50819
+ sendJson(res, 400, { ok: false, error: String(err && err.message ? err.message : err) });
50820
+ });
50821
+ return;
50822
+ }
50823
+ sendJson(res, 404, { ok: false, error: "not found" });
50824
+ }
50825
+
50826
+ var config = parseHostArgs(process.argv);
50827
+ var server = http.createServer(handleRequest);
50828
+ server.listen(config.port, "0.0.0.0", function () {
50829
+ process.stdout.write(
50830
+ "[plugin-host] listening type=" + config.type + " port=" + config.port + " version=" + VERSION + "\n"
50831
+ );
50832
+ });
50833
+ `;
50834
+ function pluginHostContentHash() {
50835
+ return (0, import_node_crypto15.createHash)("sha256").update(`${PLUGIN_HOST_VERSION}:${PLUGIN_HOST_SERVER_SOURCE}`).digest("hex").slice(0, 16);
50836
+ }
50837
+
50838
+ // src/sandbox_plugin/host/pluginHostInstaller.ts
50839
+ var PLUGIN_HOST_DIR = "/root/.agents/.plugin-host";
50840
+ var SERVER_PATH = `${PLUGIN_HOST_DIR}/server.js`;
50841
+ var VERSION_PATH = `${PLUGIN_HOST_DIR}/version.json`;
50842
+ async function installPluginHost(sandbox) {
50843
+ const hash = pluginHostContentHash();
50844
+ try {
50845
+ const existing = await sandbox.file.readFile(VERSION_PATH);
50846
+ const parsed = JSON.parse(existing.content);
50847
+ if (parsed.hash === hash) {
50848
+ return { path: SERVER_PATH, version: PLUGIN_HOST_VERSION, hash, changed: false };
50849
+ }
50850
+ } catch {
50851
+ }
50852
+ await sandbox.file.createDirectory(PLUGIN_HOST_DIR).catch(() => void 0);
50853
+ await sandbox.file.writeFile(SERVER_PATH, PLUGIN_HOST_SERVER_SOURCE);
50854
+ await sandbox.file.writeFile(VERSION_PATH, JSON.stringify({ version: PLUGIN_HOST_VERSION, hash }, null, 2));
50855
+ return { path: SERVER_PATH, version: PLUGIN_HOST_VERSION, hash, changed: true };
50856
+ }
50857
+
49298
50858
  // src/workflow/index.ts
49299
50859
  init_compile();
49300
50860
  init_parse_yaml();
@@ -49686,16 +51246,16 @@ var IdRemapper = class {
49686
51246
  };
49687
51247
 
49688
51248
  // src/util/directRoomId.ts
49689
- var import_node_crypto14 = require("crypto");
51249
+ var import_node_crypto16 = require("crypto");
49690
51250
  function generateDirectHumanProjectId(tenantId2, workspaceId, userId1, userId2) {
49691
51251
  const sortedUserIds = [userId1, userId2].sort();
49692
51252
  const parts = [tenantId2, workspaceId, ...sortedUserIds];
49693
- const hash = (0, import_node_crypto14.createHash)("sha256").update(parts.join(":")).digest("hex");
51253
+ const hash = (0, import_node_crypto16.createHash)("sha256").update(parts.join(":")).digest("hex");
49694
51254
  return `direct_human_${hash}`;
49695
51255
  }
49696
51256
  function generateDirectBotProjectId(tenantId2, workspaceId, userId, assistantId) {
49697
51257
  const parts = [tenantId2, workspaceId, userId, assistantId];
49698
- const hash = (0, import_node_crypto14.createHash)("sha256").update(parts.join(":")).digest("hex");
51258
+ const hash = (0, import_node_crypto16.createHash)("sha256").update(parts.join(":")).digest("hex");
49699
51259
  return `direct_bot_${hash}`;
49700
51260
  }
49701
51261
  function parseDirectRoomType(projectId) {
@@ -49725,6 +51285,7 @@ registerBuiltinPlugins();
49725
51285
  CapabilityBundleValidationError,
49726
51286
  CapabilityToolNameConflictError,
49727
51287
  CapabilityToolRegistry,
51288
+ ChannelConnectionLeader,
49728
51289
  ChunkBuffer,
49729
51290
  ChunkBufferLatticeManager,
49730
51291
  CollectionLatticeManager,
@@ -49736,7 +51297,9 @@ registerBuiltinPlugins();
49736
51297
  DEFAULT_CALIBRATION_PROBES,
49737
51298
  DEFAULT_CONNECT_TIMEOUT_MS,
49738
51299
  DEFAULT_DELAY_MS,
51300
+ DEFAULT_HANDLER_TIMEOUT_MS,
49739
51301
  DEFAULT_MAX_ATTEMPTS,
51302
+ DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS,
49740
51303
  DaytonaInstance,
49741
51304
  DaytonaProvider,
49742
51305
  DefaultScheduleClient,
@@ -49754,6 +51317,7 @@ registerBuiltinPlugins();
49754
51317
  HEALTH_CHECK_INTERVAL_MS,
49755
51318
  HEALTH_CHECK_TIMEOUT_MS,
49756
51319
  HumanMessage,
51320
+ INLINE_INPUT_LIMIT_BYTES,
49757
51321
  IdRemapper,
49758
51322
  InMemoryA2AApiKeyStore,
49759
51323
  InMemoryAgentWebAppStore,
@@ -49794,10 +51358,12 @@ registerBuiltinPlugins();
49794
51358
  LocalSandboxProvider,
49795
51359
  LoggerLatticeManager,
49796
51360
  MAX_BACKOFF_MS,
51361
+ MAX_HANDLER_TIMEOUT_MS,
49797
51362
  MAX_LINE_LENGTH,
49798
51363
  MAX_PACK_ENTRIES,
49799
51364
  MAX_PACK_FILE_BYTES,
49800
51365
  MAX_PACK_TOTAL_BYTES,
51366
+ MIN_HANDLER_TIMEOUT_MS,
49801
51367
  ManagedTaskStartError,
49802
51368
  McpLatticeManager,
49803
51369
  MemoryBackend,
@@ -49816,8 +51382,12 @@ registerBuiltinPlugins();
49816
51382
  PACK_DIRECTORY_TIMEOUT_SECONDS,
49817
51383
  PLATFORM_SECRET_ENV_VARS,
49818
51384
  PLUGIN_CATALOG_CHANGED_TOPIC,
51385
+ PLUGIN_HOST_DIR,
51386
+ PLUGIN_HOST_SERVER_SOURCE,
51387
+ PLUGIN_HOST_VERSION,
49819
51388
  PersonalAssistantConfig,
49820
51389
  PinoLoggerClient,
51390
+ PluginDeploymentRuntimeRegistry,
49821
51391
  PluginRegistry,
49822
51392
  PostgresDatabase,
49823
51393
  ProjectBotMembershipIdConflictError,
@@ -49879,7 +51449,9 @@ registerBuiltinPlugins();
49879
51449
  appendUiFence,
49880
51450
  assertSafeUpstreamUrl,
49881
51451
  buildBundleFromDist,
51452
+ buildConnectionPayload,
49882
51453
  buildGrepResultsDict,
51454
+ buildHandlerCommand,
49883
51455
  buildInput,
49884
51456
  buildNamedVolumeName,
49885
51457
  buildRemotePackDirectoryCommand,
@@ -49947,9 +51519,11 @@ registerBuiltinPlugins();
49947
51519
  decrypt,
49948
51520
  decryptConfigFields,
49949
51521
  describeCronExpression,
51522
+ disconnectAllChannels,
49950
51523
  documentLearningPlugin,
49951
51524
  documentParserPlugin,
49952
51525
  embeddingsLatticeManager,
51526
+ encodeInput,
49953
51527
  encrypt,
49954
51528
  encryptConfigFields,
49955
51529
  ensureBuiltinAgentsForTenant,
@@ -49993,6 +51567,7 @@ registerBuiltinPlugins();
49993
51567
  getModelLattice,
49994
51568
  getNextCronTime,
49995
51569
  getOrCreateCollectionVectorStore,
51570
+ getPluginDeploymentService,
49996
51571
  getPluginSkillManifestVersion,
49997
51572
  getQueueLattice,
49998
51573
  getReplicaId,
@@ -50021,6 +51596,8 @@ registerBuiltinPlugins();
50021
51596
  hasChunkBuffer,
50022
51597
  hasDelegatedWorkerToken,
50023
51598
  hasRuntimeCapabilityPolicy,
51599
+ installPluginHost,
51600
+ invokeResident,
50024
51601
  invokeWithRetry,
50025
51602
  isBuiltInSkill,
50026
51603
  isDirectRoom,
@@ -50048,6 +51625,8 @@ registerBuiltinPlugins();
50048
51625
  parallelLimit,
50049
51626
  parseCronExpression,
50050
51627
  parseDirectRoomType,
51628
+ parseHandlerEnvelope,
51629
+ parseHostArgs,
50051
51630
  parseJudgeVerdict,
50052
51631
  parsePackSize,
50053
51632
  parseQueuedExecutionMode,
@@ -50056,6 +51635,7 @@ registerBuiltinPlugins();
50056
51635
  parseTrustedRunContext,
50057
51636
  parseYaml,
50058
51637
  performStringReplacement,
51638
+ pluginHostContentHash,
50059
51639
  postProcessTaskMutationResponse,
50060
51640
  prepareTaskDispatch,
50061
51641
  projectMutationMutex,
@@ -50094,6 +51674,7 @@ registerBuiltinPlugins();
50094
51674
  renderTemplate,
50095
51675
  resetReplicaIdForTests,
50096
51676
  resolveEnabledPluginSkill,
51677
+ resolveHandlerTimeoutMs,
50097
51678
  resolveJudgeModelKey,
50098
51679
  resolvePath,
50099
51680
  resolvePluginConnections,
@@ -50113,6 +51694,7 @@ registerBuiltinPlugins();
50113
51694
  setBindingRegistry,
50114
51695
  setEvalRunService,
50115
51696
  setMenuRegistry,
51697
+ setPluginDeploymentService,
50116
51698
  setSandboxPluginCatalog,
50117
51699
  skillLatticeManager,
50118
51700
  sqlDatabaseManager,