@axiom-lattice/core 6.3.3 → 6.4.1
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.d.mts +880 -8
- package/dist/index.d.ts +880 -8
- package/dist/index.js +1907 -116
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1882 -114
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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,
|
|
@@ -1914,9 +1929,12 @@ __export(index_exports, {
|
|
|
1914
1929
|
globSearchFiles: () => globSearchFiles,
|
|
1915
1930
|
grepMatchesFromFiles: () => grepMatchesFromFiles,
|
|
1916
1931
|
grepSearchFiles: () => grepSearchFiles,
|
|
1932
|
+
handlerResultText: () => handlerResultText,
|
|
1917
1933
|
hasChunkBuffer: () => hasChunkBuffer,
|
|
1918
1934
|
hasDelegatedWorkerToken: () => hasDelegatedWorkerToken,
|
|
1919
1935
|
hasRuntimeCapabilityPolicy: () => hasRuntimeCapabilityPolicy,
|
|
1936
|
+
installPluginHost: () => installPluginHost,
|
|
1937
|
+
invokeResident: () => invokeResident,
|
|
1920
1938
|
invokeWithRetry: () => invokeWithRetry,
|
|
1921
1939
|
isBuiltInSkill: () => isBuiltInSkill,
|
|
1922
1940
|
isDirectRoom: () => isDirectRoom,
|
|
@@ -1944,6 +1962,8 @@ __export(index_exports, {
|
|
|
1944
1962
|
parallelLimit: () => parallelLimit,
|
|
1945
1963
|
parseCronExpression: () => parseCronExpression,
|
|
1946
1964
|
parseDirectRoomType: () => parseDirectRoomType,
|
|
1965
|
+
parseHandlerEnvelope: () => parseHandlerEnvelope,
|
|
1966
|
+
parseHostArgs: () => parseHostArgs,
|
|
1947
1967
|
parseJudgeVerdict: () => parseJudgeVerdict,
|
|
1948
1968
|
parsePackSize: () => parsePackSize,
|
|
1949
1969
|
parseQueuedExecutionMode: () => import_protocols2.parseQueuedExecutionMode,
|
|
@@ -1952,6 +1972,7 @@ __export(index_exports, {
|
|
|
1952
1972
|
parseTrustedRunContext: () => import_protocols2.parseTrustedRunContext,
|
|
1953
1973
|
parseYaml: () => parseYaml,
|
|
1954
1974
|
performStringReplacement: () => performStringReplacement,
|
|
1975
|
+
pluginHostContentHash: () => pluginHostContentHash,
|
|
1955
1976
|
postProcessTaskMutationResponse: () => postProcessTaskMutationResponse,
|
|
1956
1977
|
prepareTaskDispatch: () => prepareTaskDispatch,
|
|
1957
1978
|
projectMutationMutex: () => projectMutationMutex,
|
|
@@ -1990,6 +2011,7 @@ __export(index_exports, {
|
|
|
1990
2011
|
renderTemplate: () => renderTemplate,
|
|
1991
2012
|
resetReplicaIdForTests: () => resetReplicaIdForTests,
|
|
1992
2013
|
resolveEnabledPluginSkill: () => resolveEnabledPluginSkill,
|
|
2014
|
+
resolveHandlerTimeoutMs: () => resolveHandlerTimeoutMs,
|
|
1993
2015
|
resolveJudgeModelKey: () => resolveJudgeModelKey,
|
|
1994
2016
|
resolvePath: () => resolvePath,
|
|
1995
2017
|
resolvePluginConnections: () => resolvePluginConnections,
|
|
@@ -2009,6 +2031,7 @@ __export(index_exports, {
|
|
|
2009
2031
|
setBindingRegistry: () => setBindingRegistry,
|
|
2010
2032
|
setEvalRunService: () => setEvalRunService,
|
|
2011
2033
|
setMenuRegistry: () => setMenuRegistry,
|
|
2034
|
+
setPluginDeploymentService: () => setPluginDeploymentService,
|
|
2012
2035
|
setSandboxPluginCatalog: () => setSandboxPluginCatalog,
|
|
2013
2036
|
skillLatticeManager: () => skillLatticeManager,
|
|
2014
2037
|
sqlDatabaseManager: () => sqlDatabaseManager,
|
|
@@ -3706,6 +3729,9 @@ var InMemoryProjectStore = class {
|
|
|
3706
3729
|
workspaceId,
|
|
3707
3730
|
name: data.name,
|
|
3708
3731
|
description: data.description,
|
|
3732
|
+
// Persist `kind` like the Local/PostgreSQL stores so `public`/`personal`/
|
|
3733
|
+
// `training` project semantics survive in in-memory deployments.
|
|
3734
|
+
...data.kind === void 0 ? {} : { kind: data.kind },
|
|
3709
3735
|
config: data.config === void 0 && Object.keys(capabilityBundleIds).length === 0 ? void 0 : { ...data.config ?? {}, ...capabilityBundleIds },
|
|
3710
3736
|
createdAt: existing?.createdAt ?? now,
|
|
3711
3737
|
updatedAt: now
|
|
@@ -5325,6 +5351,93 @@ var InMemoryOutboxStore = class {
|
|
|
5325
5351
|
}
|
|
5326
5352
|
};
|
|
5327
5353
|
|
|
5354
|
+
// src/store_lattice/InMemoryPluginDeploymentStore.ts
|
|
5355
|
+
var InMemoryPluginDeploymentStore = class {
|
|
5356
|
+
constructor() {
|
|
5357
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
5358
|
+
}
|
|
5359
|
+
/**
|
|
5360
|
+
* Lists every deployment for a tenant.
|
|
5361
|
+
*
|
|
5362
|
+
* @param tenantId - Tenant whose deployments are listed
|
|
5363
|
+
* @returns Defensive copies of the tenant's deployments
|
|
5364
|
+
*/
|
|
5365
|
+
async listByTenant(tenantId2) {
|
|
5366
|
+
return [...this.entries.values()].filter((entry) => entry.tenantId === tenantId2).map((entry) => structuredClone(entry));
|
|
5367
|
+
}
|
|
5368
|
+
/**
|
|
5369
|
+
* Returns the deployment for a tenant + plugin type.
|
|
5370
|
+
*
|
|
5371
|
+
* @param tenantId - Owning tenant
|
|
5372
|
+
* @param pluginType - Plugin type key
|
|
5373
|
+
* @returns Defensive copy of the deployment, or undefined when absent
|
|
5374
|
+
*/
|
|
5375
|
+
async get(tenantId2, pluginType) {
|
|
5376
|
+
const entry = this.entries.get(entryKey2(tenantId2, pluginType));
|
|
5377
|
+
return entry ? structuredClone(entry) : void 0;
|
|
5378
|
+
}
|
|
5379
|
+
/**
|
|
5380
|
+
* Inserts or replaces a deployment keyed by tenant + plugin type.
|
|
5381
|
+
*
|
|
5382
|
+
* On conflict the existing `id` and `createdAt` are preserved while every
|
|
5383
|
+
* other field (including `updatedAt`, when supplied) is updated — mirroring
|
|
5384
|
+
* the PostgreSQL store's `ON CONFLICT` semantics.
|
|
5385
|
+
*
|
|
5386
|
+
* @param deployment - Deployment record to persist
|
|
5387
|
+
* @returns Defensive copy of the stored deployment
|
|
5388
|
+
*/
|
|
5389
|
+
async upsert(deployment) {
|
|
5390
|
+
const key5 = entryKey2(deployment.tenantId, deployment.pluginType);
|
|
5391
|
+
const existing = this.entries.get(key5);
|
|
5392
|
+
const next = {
|
|
5393
|
+
...structuredClone(deployment),
|
|
5394
|
+
id: existing?.id ?? deployment.id,
|
|
5395
|
+
createdAt: existing?.createdAt ?? deployment.createdAt,
|
|
5396
|
+
updatedAt: deployment.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5397
|
+
};
|
|
5398
|
+
this.entries.set(key5, next);
|
|
5399
|
+
return structuredClone(next);
|
|
5400
|
+
}
|
|
5401
|
+
/**
|
|
5402
|
+
* Applies a partial update to a deployment.
|
|
5403
|
+
*
|
|
5404
|
+
* `patch.createdAt` is ignored (immutable) and `updatedAt` is taken from the
|
|
5405
|
+
* patch when supplied, otherwise set to the current time — matching the
|
|
5406
|
+
* PostgreSQL store.
|
|
5407
|
+
*
|
|
5408
|
+
* @param tenantId - Owning tenant
|
|
5409
|
+
* @param pluginType - Plugin type key
|
|
5410
|
+
* @param patch - Mutable fields to apply
|
|
5411
|
+
* @returns Defensive copy of the updated deployment, or undefined when absent
|
|
5412
|
+
*/
|
|
5413
|
+
async updateStatus(tenantId2, pluginType, patch) {
|
|
5414
|
+
const key5 = entryKey2(tenantId2, pluginType);
|
|
5415
|
+
const existing = this.entries.get(key5);
|
|
5416
|
+
if (!existing) return void 0;
|
|
5417
|
+
const next = {
|
|
5418
|
+
...existing,
|
|
5419
|
+
...patch,
|
|
5420
|
+
createdAt: existing.createdAt,
|
|
5421
|
+
updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
5422
|
+
};
|
|
5423
|
+
this.entries.set(key5, next);
|
|
5424
|
+
return structuredClone(next);
|
|
5425
|
+
}
|
|
5426
|
+
/**
|
|
5427
|
+
* Deletes a deployment by tenant + plugin type.
|
|
5428
|
+
*
|
|
5429
|
+
* @param tenantId - Owning tenant
|
|
5430
|
+
* @param pluginType - Plugin type key
|
|
5431
|
+
* @returns Whether a record was removed
|
|
5432
|
+
*/
|
|
5433
|
+
async delete(tenantId2, pluginType) {
|
|
5434
|
+
return this.entries.delete(entryKey2(tenantId2, pluginType));
|
|
5435
|
+
}
|
|
5436
|
+
};
|
|
5437
|
+
function entryKey2(tenantId2, pluginType) {
|
|
5438
|
+
return JSON.stringify([tenantId2, pluginType]);
|
|
5439
|
+
}
|
|
5440
|
+
|
|
5328
5441
|
// src/store_lattice/InMemoryUserStore.ts
|
|
5329
5442
|
var InMemoryUserStore = class {
|
|
5330
5443
|
constructor() {
|
|
@@ -6987,6 +7100,7 @@ var defaultProjectRoomReadStateStore = new InMemoryProjectRoomReadStateStore();
|
|
|
6987
7100
|
var defaultUserPushSubscriptionStore = new InMemoryUserPushSubscriptionStore();
|
|
6988
7101
|
var defaultLeaseStore = new InMemoryLeaseStore();
|
|
6989
7102
|
var defaultOutboxStore = new InMemoryOutboxStore();
|
|
7103
|
+
var defaultPluginDeploymentStore = new InMemoryPluginDeploymentStore();
|
|
6990
7104
|
storeLatticeManager.registerLattice("default", "thread", defaultThreadStore);
|
|
6991
7105
|
storeLatticeManager.registerLattice(
|
|
6992
7106
|
"default",
|
|
@@ -7010,6 +7124,7 @@ storeLatticeManager.registerLattice("default", "projectRoomReadState", defaultPr
|
|
|
7010
7124
|
storeLatticeManager.registerLattice("default", "userPushSubscription", defaultUserPushSubscriptionStore);
|
|
7011
7125
|
storeLatticeManager.registerLattice("default", "lease", defaultLeaseStore);
|
|
7012
7126
|
storeLatticeManager.registerLattice("default", "outbox", defaultOutboxStore);
|
|
7127
|
+
storeLatticeManager.registerLattice("default", "pluginDeployment", defaultPluginDeploymentStore);
|
|
7013
7128
|
var defaultUserStore = new InMemoryUserStore();
|
|
7014
7129
|
var defaultTenantStore = new InMemoryTenantStore();
|
|
7015
7130
|
var defaultUserTenantLinkStore = new InMemoryUserTenantLinkStore();
|
|
@@ -15069,6 +15184,82 @@ var evalPlugin = {
|
|
|
15069
15184
|
})
|
|
15070
15185
|
};
|
|
15071
15186
|
|
|
15187
|
+
// src/sandbox_plugin/residentTransport.ts
|
|
15188
|
+
var DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS = 6e4;
|
|
15189
|
+
function isRecord3(value) {
|
|
15190
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15191
|
+
}
|
|
15192
|
+
function isEnvelope(value) {
|
|
15193
|
+
return isRecord3(value) && typeof value.ok === "boolean";
|
|
15194
|
+
}
|
|
15195
|
+
function errorFromBody(value) {
|
|
15196
|
+
if (isRecord3(value) && typeof value.error === "string" && value.error.length > 0) {
|
|
15197
|
+
return value.error;
|
|
15198
|
+
}
|
|
15199
|
+
return void 0;
|
|
15200
|
+
}
|
|
15201
|
+
async function readJson(response) {
|
|
15202
|
+
try {
|
|
15203
|
+
return await response.json();
|
|
15204
|
+
} catch {
|
|
15205
|
+
return void 0;
|
|
15206
|
+
}
|
|
15207
|
+
}
|
|
15208
|
+
async function invokeResident(args) {
|
|
15209
|
+
const {
|
|
15210
|
+
baseUrl,
|
|
15211
|
+
endpointPath,
|
|
15212
|
+
serviceKey,
|
|
15213
|
+
payload,
|
|
15214
|
+
timeoutMs = DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS
|
|
15215
|
+
} = args;
|
|
15216
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
15217
|
+
const url = `${baseUrl.replace(/\/$/, "")}${endpointPath}/invoke`;
|
|
15218
|
+
const headers = {
|
|
15219
|
+
"Content-Type": "application/json"
|
|
15220
|
+
};
|
|
15221
|
+
if (serviceKey) {
|
|
15222
|
+
headers["Authorization"] = `Bearer ${serviceKey}`;
|
|
15223
|
+
}
|
|
15224
|
+
const controller = new AbortController();
|
|
15225
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
15226
|
+
try {
|
|
15227
|
+
const response = await fetchImpl(url, {
|
|
15228
|
+
method: "POST",
|
|
15229
|
+
headers,
|
|
15230
|
+
body: JSON.stringify({ ...payload, timeoutMs: payload.timeoutMs ?? timeoutMs }),
|
|
15231
|
+
signal: controller.signal
|
|
15232
|
+
});
|
|
15233
|
+
const parsed = await readJson(response);
|
|
15234
|
+
if (!response.ok) {
|
|
15235
|
+
return {
|
|
15236
|
+
ok: false,
|
|
15237
|
+
error: errorFromBody(parsed) ?? `resident service responded with HTTP ${response.status}`,
|
|
15238
|
+
status: response.status
|
|
15239
|
+
};
|
|
15240
|
+
}
|
|
15241
|
+
if (!isEnvelope(parsed)) {
|
|
15242
|
+
return {
|
|
15243
|
+
ok: false,
|
|
15244
|
+
error: "resident service returned an invalid envelope",
|
|
15245
|
+
status: response.status
|
|
15246
|
+
};
|
|
15247
|
+
}
|
|
15248
|
+
if (!parsed.ok) {
|
|
15249
|
+
return {
|
|
15250
|
+
ok: false,
|
|
15251
|
+
error: errorFromBody(parsed) ?? "resident handler reported failure",
|
|
15252
|
+
status: response.status
|
|
15253
|
+
};
|
|
15254
|
+
}
|
|
15255
|
+
return { ok: true, result: parsed.result, status: response.status };
|
|
15256
|
+
} catch (error) {
|
|
15257
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
15258
|
+
} finally {
|
|
15259
|
+
clearTimeout(timer);
|
|
15260
|
+
}
|
|
15261
|
+
}
|
|
15262
|
+
|
|
15072
15263
|
// src/sandbox_plugin/SandboxPluginExecutor.ts
|
|
15073
15264
|
var PLUGINS_ROOT = "/root/.agents/plugins";
|
|
15074
15265
|
var INLINE_INPUT_LIMIT_BYTES = 48 * 1024;
|
|
@@ -15081,11 +15272,11 @@ var MAX_MAX_RESULT_BYTES = 1048576;
|
|
|
15081
15272
|
function encode(value) {
|
|
15082
15273
|
return Buffer.from(JSON.stringify(value ?? null), "utf8").toString("base64");
|
|
15083
15274
|
}
|
|
15084
|
-
function
|
|
15275
|
+
function isRecord4(value) {
|
|
15085
15276
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15086
15277
|
}
|
|
15087
|
-
function
|
|
15088
|
-
return
|
|
15278
|
+
function isEnvelope2(value) {
|
|
15279
|
+
return isRecord4(value) && typeof value.ok === "boolean";
|
|
15089
15280
|
}
|
|
15090
15281
|
function clamp(value, min, max) {
|
|
15091
15282
|
return Math.min(Math.max(value, min), max);
|
|
@@ -15106,7 +15297,7 @@ function parseEnvelope(stdout) {
|
|
|
15106
15297
|
const trimmed = stdout.trim();
|
|
15107
15298
|
try {
|
|
15108
15299
|
const parsed = JSON.parse(trimmed || "null");
|
|
15109
|
-
if (
|
|
15300
|
+
if (isEnvelope2(parsed)) return parsed;
|
|
15110
15301
|
} catch {
|
|
15111
15302
|
}
|
|
15112
15303
|
const lines = stdout.split("\n");
|
|
@@ -15115,7 +15306,7 @@ function parseEnvelope(stdout) {
|
|
|
15115
15306
|
if (!line) continue;
|
|
15116
15307
|
try {
|
|
15117
15308
|
const parsed = JSON.parse(line);
|
|
15118
|
-
if (
|
|
15309
|
+
if (isEnvelope2(parsed)) return parsed;
|
|
15119
15310
|
} catch {
|
|
15120
15311
|
}
|
|
15121
15312
|
}
|
|
@@ -15128,26 +15319,264 @@ function truncate(value, cap) {
|
|
|
15128
15319
|
while (end > 0 && (buf[end] & 192) === 128) end -= 1;
|
|
15129
15320
|
return buf.subarray(0, end).toString("utf8") + "\n...[truncated]";
|
|
15130
15321
|
}
|
|
15322
|
+
function capResult(result, cap) {
|
|
15323
|
+
const serialized = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
15324
|
+
return Buffer.byteLength(serialized, "utf8") <= cap ? result : truncate(serialized, cap);
|
|
15325
|
+
}
|
|
15326
|
+
function handlerResultText(result) {
|
|
15327
|
+
const value = result.result;
|
|
15328
|
+
if (typeof value === "string") return value;
|
|
15329
|
+
if (value === void 0) return (result.stdout ?? "").trim();
|
|
15330
|
+
return JSON.stringify(value);
|
|
15331
|
+
}
|
|
15131
15332
|
function errorFromStdout(stdout) {
|
|
15132
15333
|
let text = stdout;
|
|
15133
15334
|
try {
|
|
15134
15335
|
const parsed = JSON.parse(stdout);
|
|
15135
|
-
if (
|
|
15336
|
+
if (isRecord4(parsed)) {
|
|
15136
15337
|
text = JSON.stringify(sanitize(parsed)) ?? stdout;
|
|
15137
15338
|
}
|
|
15138
15339
|
} catch {
|
|
15139
15340
|
}
|
|
15140
15341
|
return text.slice(0, 4096);
|
|
15141
15342
|
}
|
|
15343
|
+
function buildResidentPayload(args, timeoutMs) {
|
|
15344
|
+
return {
|
|
15345
|
+
handler: args.handler,
|
|
15346
|
+
input: args.input,
|
|
15347
|
+
...args.connection === void 0 ? {} : { connection: args.connection },
|
|
15348
|
+
...args.connections === void 0 ? {} : { connections: args.connections },
|
|
15349
|
+
context: args.context,
|
|
15350
|
+
timeoutMs
|
|
15351
|
+
};
|
|
15352
|
+
}
|
|
15142
15353
|
var SandboxPluginExecutor = class {
|
|
15143
15354
|
/**
|
|
15144
|
-
* @param deps - Sandbox resolver and optional
|
|
15355
|
+
* @param deps - Sandbox resolver, optional logger, and optional resident routing
|
|
15145
15356
|
*/
|
|
15146
15357
|
constructor(deps) {
|
|
15147
15358
|
this.deps = deps;
|
|
15148
15359
|
}
|
|
15149
15360
|
/**
|
|
15150
|
-
* Invoke a plugin handler
|
|
15361
|
+
* Invoke a plugin handler.
|
|
15362
|
+
*
|
|
15363
|
+
* Routes to the published plugin's resident service when {@link
|
|
15364
|
+
* SandboxPluginExecutorDeps.resident} is configured and a deployment is
|
|
15365
|
+
* routable (`running` with an endpoint, or a resumed `onDemand` deployment).
|
|
15366
|
+
* Otherwise, or when the resident call fails and degradation is enabled, runs
|
|
15367
|
+
* the handler as a short `node <handler>` process inside the tenant sandbox.
|
|
15368
|
+
*
|
|
15369
|
+
* @param args - Invocation arguments including manifest, handler, and input
|
|
15370
|
+
* @returns The normalized handler result, exit code, timing, and raw stdout
|
|
15371
|
+
* @throws When `args.handler` is not a safe relative path
|
|
15372
|
+
*/
|
|
15373
|
+
async invoke(args) {
|
|
15374
|
+
const started = Date.now();
|
|
15375
|
+
validateRelativePath(args.handler, "handler");
|
|
15376
|
+
if (this.deps.resident) {
|
|
15377
|
+
const resident = await this.tryResident(args, this.deps.resident, started);
|
|
15378
|
+
if (resident) return resident;
|
|
15379
|
+
}
|
|
15380
|
+
return this.invokeShortProcess(args, started);
|
|
15381
|
+
}
|
|
15382
|
+
/**
|
|
15383
|
+
* Attempt to route an invocation to a published plugin's resident service.
|
|
15384
|
+
*
|
|
15385
|
+
* @param args - Invocation arguments
|
|
15386
|
+
* @param resident - Resident routing dependencies
|
|
15387
|
+
* @param started - Invocation start timestamp used for `durationMs`
|
|
15388
|
+
* @returns A terminal result (success, or failure with degradation disabled),
|
|
15389
|
+
* or `undefined` when the caller should fall back to the short-process path
|
|
15390
|
+
*/
|
|
15391
|
+
async tryResident(args, resident, started) {
|
|
15392
|
+
const tenantId2 = args.runConfig?.tenantId ?? args.context.tenantId;
|
|
15393
|
+
const pluginType = args.manifest.type;
|
|
15394
|
+
if (!tenantId2) return void 0;
|
|
15395
|
+
const deployment = await this.lookupDeployment(resident, tenantId2, pluginType);
|
|
15396
|
+
if (!deployment) return void 0;
|
|
15397
|
+
const timeoutMs = clamp(args.timeoutMs ?? DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
15398
|
+
const payload = buildResidentPayload(args, timeoutMs);
|
|
15399
|
+
if (deployment.status === "running" && deployment.endpoint) {
|
|
15400
|
+
let outcome = await this.callResident(resident, deployment.endpoint, payload, timeoutMs);
|
|
15401
|
+
if (!outcome.ok && deployment.mode === "onDemand" && resident.ensureRunning) {
|
|
15402
|
+
outcome = await this.ensureRunningAndRetry(
|
|
15403
|
+
resident,
|
|
15404
|
+
tenantId2,
|
|
15405
|
+
pluginType,
|
|
15406
|
+
deployment.endpoint,
|
|
15407
|
+
payload,
|
|
15408
|
+
timeoutMs,
|
|
15409
|
+
outcome
|
|
15410
|
+
);
|
|
15411
|
+
}
|
|
15412
|
+
return this.finalizeResident(outcome, args, resident, pluginType, tenantId2, started);
|
|
15413
|
+
}
|
|
15414
|
+
if (deployment.mode === "onDemand" && resident.ensureRunning) {
|
|
15415
|
+
const outcome = await this.wakeOnDemand(resident, tenantId2, pluginType, payload, timeoutMs);
|
|
15416
|
+
if (outcome) {
|
|
15417
|
+
return this.finalizeResident(outcome, args, resident, pluginType, tenantId2, started);
|
|
15418
|
+
}
|
|
15419
|
+
}
|
|
15420
|
+
return void 0;
|
|
15421
|
+
}
|
|
15422
|
+
/**
|
|
15423
|
+
* Normalize a resident invocation outcome into the caller-facing result.
|
|
15424
|
+
*
|
|
15425
|
+
* @param outcome - Normalized resident transport result
|
|
15426
|
+
* @param args - Invocation arguments (for the result-size cap)
|
|
15427
|
+
* @param resident - Resident routing dependencies (degradation policy)
|
|
15428
|
+
* @param pluginType - Plugin type, for diagnostics
|
|
15429
|
+
* @param tenantId - Tenant owning the plugin, for diagnostics
|
|
15430
|
+
* @param started - Invocation start timestamp used for `durationMs`
|
|
15431
|
+
* @returns The normalized result, or `undefined` when the caller should fall
|
|
15432
|
+
* back to the short-process path
|
|
15433
|
+
*/
|
|
15434
|
+
finalizeResident(outcome, args, resident, pluginType, tenantId2, started) {
|
|
15435
|
+
if (outcome.ok) {
|
|
15436
|
+
const cap = clamp(
|
|
15437
|
+
args.maxResultBytes ?? DEFAULT_MAX_RESULT_BYTES,
|
|
15438
|
+
MIN_MAX_RESULT_BYTES,
|
|
15439
|
+
MAX_MAX_RESULT_BYTES
|
|
15440
|
+
);
|
|
15441
|
+
return {
|
|
15442
|
+
ok: true,
|
|
15443
|
+
result: capResult(outcome.result, cap),
|
|
15444
|
+
exitCode: 0,
|
|
15445
|
+
httpStatus: outcome.status,
|
|
15446
|
+
durationMs: Date.now() - started,
|
|
15447
|
+
stdout: "",
|
|
15448
|
+
stderr: ""
|
|
15449
|
+
};
|
|
15450
|
+
}
|
|
15451
|
+
const error = outcome.error ?? "resident plugin invocation failed";
|
|
15452
|
+
if (resident.degradeToShortProcess ?? true) {
|
|
15453
|
+
this.deps.logger?.info(
|
|
15454
|
+
{ event: "sandbox-plugin.resident.degrade", pluginType, tenantId: tenantId2, error },
|
|
15455
|
+
"resident plugin invocation failed; falling back to short process"
|
|
15456
|
+
);
|
|
15457
|
+
return void 0;
|
|
15458
|
+
}
|
|
15459
|
+
return {
|
|
15460
|
+
ok: false,
|
|
15461
|
+
error,
|
|
15462
|
+
exitCode: 1,
|
|
15463
|
+
httpStatus: outcome.status,
|
|
15464
|
+
durationMs: Date.now() - started,
|
|
15465
|
+
stdout: "",
|
|
15466
|
+
stderr: ""
|
|
15467
|
+
};
|
|
15468
|
+
}
|
|
15469
|
+
/**
|
|
15470
|
+
* Look up a deployment, degrading to `undefined` when the lookup throws.
|
|
15471
|
+
*
|
|
15472
|
+
* @param resident - Resident routing dependencies
|
|
15473
|
+
* @param tenantId - Tenant owning the plugin
|
|
15474
|
+
* @param pluginType - Plugin type addressed by the invocation
|
|
15475
|
+
* @returns The deployment, or `undefined` when absent or lookup failed
|
|
15476
|
+
*/
|
|
15477
|
+
async lookupDeployment(resident, tenantId2, pluginType) {
|
|
15478
|
+
try {
|
|
15479
|
+
return await resident.getDeployment(tenantId2, pluginType);
|
|
15480
|
+
} catch (error) {
|
|
15481
|
+
this.deps.logger?.info(
|
|
15482
|
+
{
|
|
15483
|
+
event: "sandbox-plugin.resident.lookup.failed",
|
|
15484
|
+
pluginType,
|
|
15485
|
+
tenantId: tenantId2,
|
|
15486
|
+
error: error instanceof Error ? error.message : String(error)
|
|
15487
|
+
},
|
|
15488
|
+
"resident deployment lookup failed; falling back to short process"
|
|
15489
|
+
);
|
|
15490
|
+
return void 0;
|
|
15491
|
+
}
|
|
15492
|
+
}
|
|
15493
|
+
/**
|
|
15494
|
+
* Resume a non-running `onDemand` deployment once, then invoke it.
|
|
15495
|
+
*
|
|
15496
|
+
* Re-reads the deployment after the resume so a cold-started endpoint is
|
|
15497
|
+
* picked up. Never throws: a failed resume or a deployment that is still not
|
|
15498
|
+
* `running` with an endpoint resolves to `undefined`, letting the caller use
|
|
15499
|
+
* the short-process path.
|
|
15500
|
+
*
|
|
15501
|
+
* @param resident - Resident routing dependencies
|
|
15502
|
+
* @param tenantId - Tenant owning the plugin
|
|
15503
|
+
* @param pluginType - Plugin type addressed by the invocation
|
|
15504
|
+
* @param payload - Invocation payload
|
|
15505
|
+
* @param timeoutMs - Clamped per-call timeout
|
|
15506
|
+
* @returns The resident outcome when routed, or `undefined` to short-process
|
|
15507
|
+
*/
|
|
15508
|
+
async wakeOnDemand(resident, tenantId2, pluginType, payload, timeoutMs) {
|
|
15509
|
+
try {
|
|
15510
|
+
await resident.ensureRunning?.(tenantId2, pluginType);
|
|
15511
|
+
} catch (error) {
|
|
15512
|
+
this.deps.logger?.info(
|
|
15513
|
+
{
|
|
15514
|
+
event: "sandbox-plugin.resident.ensure.failed",
|
|
15515
|
+
pluginType,
|
|
15516
|
+
tenantId: tenantId2,
|
|
15517
|
+
error: error instanceof Error ? error.message : String(error)
|
|
15518
|
+
},
|
|
15519
|
+
"resident service resume failed; falling back to short process"
|
|
15520
|
+
);
|
|
15521
|
+
return void 0;
|
|
15522
|
+
}
|
|
15523
|
+
const refreshed = await this.lookupDeployment(resident, tenantId2, pluginType);
|
|
15524
|
+
if (!refreshed || refreshed.status !== "running" || !refreshed.endpoint) {
|
|
15525
|
+
return void 0;
|
|
15526
|
+
}
|
|
15527
|
+
return this.callResident(resident, refreshed.endpoint, payload, timeoutMs);
|
|
15528
|
+
}
|
|
15529
|
+
/**
|
|
15530
|
+
* Resume an `onDemand` deployment once and retry the resident call once.
|
|
15531
|
+
*
|
|
15532
|
+
* @param resident - Resident routing dependencies
|
|
15533
|
+
* @param tenantId - Tenant owning the plugin
|
|
15534
|
+
* @param pluginType - Plugin type addressed by the invocation
|
|
15535
|
+
* @param endpoint - Endpoint used by the first attempt
|
|
15536
|
+
* @param payload - Invocation payload
|
|
15537
|
+
* @param timeoutMs - Clamped per-call timeout
|
|
15538
|
+
* @param firstOutcome - Failed first attempt, returned when resume fails
|
|
15539
|
+
* @returns The retry outcome, or `firstOutcome` when resume itself failed
|
|
15540
|
+
*/
|
|
15541
|
+
async ensureRunningAndRetry(resident, tenantId2, pluginType, endpoint, payload, timeoutMs, firstOutcome) {
|
|
15542
|
+
try {
|
|
15543
|
+
await resident.ensureRunning?.(tenantId2, pluginType);
|
|
15544
|
+
} catch (error) {
|
|
15545
|
+
this.deps.logger?.info(
|
|
15546
|
+
{
|
|
15547
|
+
event: "sandbox-plugin.resident.ensure.failed",
|
|
15548
|
+
pluginType,
|
|
15549
|
+
tenantId: tenantId2,
|
|
15550
|
+
error: error instanceof Error ? error.message : String(error)
|
|
15551
|
+
},
|
|
15552
|
+
"resident service resume failed"
|
|
15553
|
+
);
|
|
15554
|
+
return firstOutcome;
|
|
15555
|
+
}
|
|
15556
|
+
const refreshed = await this.lookupDeployment(resident, tenantId2, pluginType);
|
|
15557
|
+
return this.callResident(resident, refreshed?.endpoint ?? endpoint, payload, timeoutMs);
|
|
15558
|
+
}
|
|
15559
|
+
/**
|
|
15560
|
+
* Invoke the resident host over the sandbox-service proxy path.
|
|
15561
|
+
*
|
|
15562
|
+
* @param resident - Resident routing dependencies
|
|
15563
|
+
* @param endpointPath - Relative proxy path
|
|
15564
|
+
* @param payload - Invocation payload matching the host contract
|
|
15565
|
+
* @param timeoutMs - Clamped per-call timeout
|
|
15566
|
+
* @returns The normalized resident invocation result
|
|
15567
|
+
*/
|
|
15568
|
+
callResident(resident, endpointPath, payload, timeoutMs) {
|
|
15569
|
+
return invokeResident({
|
|
15570
|
+
baseUrl: resident.baseUrl,
|
|
15571
|
+
endpointPath,
|
|
15572
|
+
serviceKey: resident.serviceKey,
|
|
15573
|
+
payload,
|
|
15574
|
+
timeoutMs,
|
|
15575
|
+
fetchImpl: resident.fetchImpl
|
|
15576
|
+
});
|
|
15577
|
+
}
|
|
15578
|
+
/**
|
|
15579
|
+
* Invoke a plugin handler as a short `node <handler>` process.
|
|
15151
15580
|
*
|
|
15152
15581
|
* Resolves the sandbox, injects small input as a base64 env var (or writes a
|
|
15153
15582
|
* temp `input.json` for large input), writes connection credentials to a temp
|
|
@@ -15157,12 +15586,10 @@ var SandboxPluginExecutor = class {
|
|
|
15157
15586
|
* in all cases, including setup failures.
|
|
15158
15587
|
*
|
|
15159
15588
|
* @param args - Invocation arguments including manifest, handler, and input
|
|
15589
|
+
* @param started - Invocation start timestamp used for `durationMs`
|
|
15160
15590
|
* @returns The normalized handler result, exit code, timing, and raw stdout
|
|
15161
|
-
* @throws When `args.handler` is not a safe relative path
|
|
15162
15591
|
*/
|
|
15163
|
-
async
|
|
15164
|
-
const started = Date.now();
|
|
15165
|
-
validateRelativePath(args.handler, "handler");
|
|
15592
|
+
async invokeShortProcess(args, started) {
|
|
15166
15593
|
const pluginDir = `${PLUGINS_ROOT}/${args.manifest.type}`;
|
|
15167
15594
|
const handlerAbs = `${pluginDir}/${args.handler}`;
|
|
15168
15595
|
const tenantId2 = args.runConfig?.tenantId ?? args.context.tenantId;
|
|
@@ -15250,11 +15677,9 @@ var SandboxPluginExecutor = class {
|
|
|
15250
15677
|
}
|
|
15251
15678
|
const parsed = parseEnvelope(stdout);
|
|
15252
15679
|
if (parsed) {
|
|
15253
|
-
const result = parsed.ok ? parsed.result : void 0;
|
|
15254
|
-
const serialized = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
15255
15680
|
return {
|
|
15256
15681
|
ok: parsed.ok,
|
|
15257
|
-
result:
|
|
15682
|
+
result: capResult(parsed.ok ? parsed.result : void 0, cap),
|
|
15258
15683
|
error: typeof parsed.error === "string" ? parsed.error : void 0,
|
|
15259
15684
|
exitCode,
|
|
15260
15685
|
durationMs,
|
|
@@ -15265,7 +15690,7 @@ var SandboxPluginExecutor = class {
|
|
|
15265
15690
|
const text = stdout.trim();
|
|
15266
15691
|
return {
|
|
15267
15692
|
ok: true,
|
|
15268
|
-
result:
|
|
15693
|
+
result: capResult(text, cap),
|
|
15269
15694
|
exitCode,
|
|
15270
15695
|
durationMs,
|
|
15271
15696
|
stdout,
|
|
@@ -15282,24 +15707,33 @@ var SandboxPluginExecutor = class {
|
|
|
15282
15707
|
}
|
|
15283
15708
|
};
|
|
15284
15709
|
|
|
15710
|
+
// src/sandbox_plugin/pluginDeploymentServiceRuntime.ts
|
|
15711
|
+
var service2 = null;
|
|
15712
|
+
function setPluginDeploymentService(instance) {
|
|
15713
|
+
service2 = instance ?? null;
|
|
15714
|
+
}
|
|
15715
|
+
function getPluginDeploymentService() {
|
|
15716
|
+
return service2;
|
|
15717
|
+
}
|
|
15718
|
+
|
|
15285
15719
|
// src/sandbox_plugin/TenantPluginCatalog.ts
|
|
15286
15720
|
var import_node_crypto3 = require("crypto");
|
|
15287
15721
|
|
|
15288
15722
|
// src/sandbox_plugin/jsonSchemaToZod.ts
|
|
15289
15723
|
var import_zod45 = require("zod");
|
|
15290
|
-
function
|
|
15724
|
+
function isRecord5(value) {
|
|
15291
15725
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15292
15726
|
}
|
|
15293
15727
|
function withDescription(schema6, raw) {
|
|
15294
|
-
const description =
|
|
15728
|
+
const description = isRecord5(raw) ? raw.description : void 0;
|
|
15295
15729
|
return typeof description === "string" ? schema6.describe(description) : schema6;
|
|
15296
15730
|
}
|
|
15297
15731
|
function jsonSchemaToZod(schema6) {
|
|
15298
|
-
if (!
|
|
15732
|
+
if (!isRecord5(schema6)) throw new Error("schema must be an object");
|
|
15299
15733
|
const type = schema6.type;
|
|
15300
15734
|
switch (type) {
|
|
15301
15735
|
case "object": {
|
|
15302
|
-
const props =
|
|
15736
|
+
const props = isRecord5(schema6.properties) ? schema6.properties : {};
|
|
15303
15737
|
const required = Array.isArray(schema6.required) ? schema6.required.filter((x) => typeof x === "string") : [];
|
|
15304
15738
|
const shape = {};
|
|
15305
15739
|
for (const [key5, sub] of Object.entries(props)) {
|
|
@@ -15339,7 +15773,7 @@ var DISPLAY_MODES = ["inline", "fullscreen", "pip"];
|
|
|
15339
15773
|
var MAX_MANIFEST_BYTES = 262144;
|
|
15340
15774
|
var MAX_TOOLS = 32;
|
|
15341
15775
|
var MAX_PLUGINS_PER_TENANT = 50;
|
|
15342
|
-
function
|
|
15776
|
+
function isRecord6(value) {
|
|
15343
15777
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15344
15778
|
}
|
|
15345
15779
|
function isSafeHandler(path8) {
|
|
@@ -15364,7 +15798,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
15364
15798
|
const diagnostics = [];
|
|
15365
15799
|
const error = (code, message, pluginType, tool68) => diagnostics.push({ level: "error", code, message, pluginType, tool: tool68 });
|
|
15366
15800
|
const raw = args.raw;
|
|
15367
|
-
if (!
|
|
15801
|
+
if (!isRecord6(raw)) {
|
|
15368
15802
|
error("MANIFEST_NOT_OBJECT", "manifest must be a JSON object", args.dirName);
|
|
15369
15803
|
return { tools: [], diagnostics };
|
|
15370
15804
|
}
|
|
@@ -15417,7 +15851,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
15417
15851
|
const tools = [];
|
|
15418
15852
|
const seenToolNames = /* @__PURE__ */ new Set();
|
|
15419
15853
|
for (const candidate of toolCandidates.slice(0, MAX_TOOLS)) {
|
|
15420
|
-
if (!
|
|
15854
|
+
if (!isRecord6(candidate)) {
|
|
15421
15855
|
error("TOOL_INVALID", "tool must be an object", type);
|
|
15422
15856
|
continue;
|
|
15423
15857
|
}
|
|
@@ -15434,7 +15868,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
15434
15868
|
error("HANDLER_MISSING", `handler "${String(candidate.handler)}" not found`, type, name);
|
|
15435
15869
|
continue;
|
|
15436
15870
|
}
|
|
15437
|
-
if (!
|
|
15871
|
+
if (!isRecord6(candidate.schema)) {
|
|
15438
15872
|
error("TOOL_SCHEMA", `tool "${name}" schema must be an object`, type, name);
|
|
15439
15873
|
continue;
|
|
15440
15874
|
}
|
|
@@ -15448,17 +15882,17 @@ function validateSandboxPluginManifest(args) {
|
|
|
15448
15882
|
let ui;
|
|
15449
15883
|
if (candidate.ui !== void 0) {
|
|
15450
15884
|
const rawUi = candidate.ui;
|
|
15451
|
-
if (!
|
|
15885
|
+
if (!isRecord6(rawUi) || !isSafeUiPath(rawUi.resource) || !args.handlerExists(rawUi.resource)) {
|
|
15452
15886
|
error("TOOL_UI", `tool "${name}" ui.resource must be an existing .html file within the plugin dir`, type, name);
|
|
15453
15887
|
} else {
|
|
15454
|
-
const csp =
|
|
15888
|
+
const csp = isRecord6(rawUi.csp) ? {
|
|
15455
15889
|
connectDomains: Array.isArray(rawUi.csp.connectDomains) ? rawUi.csp.connectDomains.filter((d) => typeof d === "string") : void 0,
|
|
15456
15890
|
resourceDomains: Array.isArray(rawUi.csp.resourceDomains) ? rawUi.csp.resourceDomains.filter((d) => typeof d === "string") : void 0,
|
|
15457
15891
|
frameDomains: Array.isArray(rawUi.csp.frameDomains) ? rawUi.csp.frameDomains.filter((d) => typeof d === "string") : void 0,
|
|
15458
15892
|
baseUriDomains: Array.isArray(rawUi.csp.baseUriDomains) ? rawUi.csp.baseUriDomains.filter((d) => typeof d === "string") : void 0
|
|
15459
15893
|
} : void 0;
|
|
15460
15894
|
const displayMode = DISPLAY_MODES.includes(rawUi.displayMode) ? rawUi.displayMode : void 0;
|
|
15461
|
-
const permissions =
|
|
15895
|
+
const permissions = isRecord6(rawUi.permissions) ? {
|
|
15462
15896
|
camera: rawUi.permissions.camera === true ? true : void 0,
|
|
15463
15897
|
microphone: rawUi.permissions.microphone === true ? true : void 0,
|
|
15464
15898
|
geolocation: rawUi.permissions.geolocation === true ? true : void 0,
|
|
@@ -15488,7 +15922,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
15488
15922
|
}
|
|
15489
15923
|
const skills = {};
|
|
15490
15924
|
if (raw.skills !== void 0) {
|
|
15491
|
-
if (!
|
|
15925
|
+
if (!isRecord6(raw.skills)) {
|
|
15492
15926
|
error("SKILLS_INVALID", "skills must be an object", type);
|
|
15493
15927
|
} else {
|
|
15494
15928
|
const prefix = `${type}-`;
|
|
@@ -15503,12 +15937,12 @@ function validateSandboxPluginManifest(args) {
|
|
|
15503
15937
|
error("SKILL_NAME", `skill "${name}" must be a safe relative path prefixed with "${prefix}"`, type);
|
|
15504
15938
|
continue;
|
|
15505
15939
|
}
|
|
15506
|
-
if (!
|
|
15940
|
+
if (!isRecord6(definition) || typeof definition.version !== "string" || typeof definition.content !== "string") {
|
|
15507
15941
|
error("SKILL_INVALID", `skill "${name}" requires string version and content`, type);
|
|
15508
15942
|
continue;
|
|
15509
15943
|
}
|
|
15510
15944
|
if (definition.resources !== void 0) {
|
|
15511
|
-
if (!
|
|
15945
|
+
if (!isRecord6(definition.resources)) {
|
|
15512
15946
|
error("SKILL_RESOURCES", `skill "${name}" resources must be an object`, type);
|
|
15513
15947
|
continue;
|
|
15514
15948
|
}
|
|
@@ -15520,7 +15954,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
15520
15954
|
resourcesOk = false;
|
|
15521
15955
|
break;
|
|
15522
15956
|
}
|
|
15523
|
-
if (!
|
|
15957
|
+
if (!isRecord6(resource) || typeof resource.content !== "string") {
|
|
15524
15958
|
resourcesOk = false;
|
|
15525
15959
|
break;
|
|
15526
15960
|
}
|
|
@@ -15536,11 +15970,11 @@ function validateSandboxPluginManifest(args) {
|
|
|
15536
15970
|
}
|
|
15537
15971
|
const agents = {};
|
|
15538
15972
|
if (raw.agents !== void 0) {
|
|
15539
|
-
if (!
|
|
15973
|
+
if (!isRecord6(raw.agents)) {
|
|
15540
15974
|
error("AGENTS_INVALID", "agents must be an object", type);
|
|
15541
15975
|
} else {
|
|
15542
15976
|
for (const [key5, config] of Object.entries(raw.agents)) {
|
|
15543
|
-
if (!
|
|
15977
|
+
if (!isRecord6(config)) {
|
|
15544
15978
|
error("AGENT_INVALID", `agent "${key5}" must be an object`, type);
|
|
15545
15979
|
continue;
|
|
15546
15980
|
}
|
|
@@ -15561,7 +15995,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
15561
15995
|
}
|
|
15562
15996
|
}
|
|
15563
15997
|
let connection;
|
|
15564
|
-
if (
|
|
15998
|
+
if (isRecord6(raw.connection)) {
|
|
15565
15999
|
const fields = raw.connection.fields;
|
|
15566
16000
|
if (!Array.isArray(fields)) {
|
|
15567
16001
|
error("CONNECTION_INVALID", "connection.fields must be an array", type);
|
|
@@ -15575,7 +16009,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
15575
16009
|
for (const key5 of ["test", "discover"]) {
|
|
15576
16010
|
const entry = raw.connection[key5];
|
|
15577
16011
|
if (entry === void 0) continue;
|
|
15578
|
-
const handler =
|
|
16012
|
+
const handler = isRecord6(entry) ? entry.handler : void 0;
|
|
15579
16013
|
if (!isSafeHandler(handler) || !args.handlerExists(handler)) {
|
|
15580
16014
|
error("CONNECTION_HANDLER", `connection.${key5} handler "${String(handler)}" not found`, type);
|
|
15581
16015
|
continue;
|
|
@@ -15593,8 +16027,8 @@ function validateSandboxPluginManifest(args) {
|
|
|
15593
16027
|
version: raw.version,
|
|
15594
16028
|
icon: typeof raw.icon === "string" ? raw.icon : void 0,
|
|
15595
16029
|
category: typeof raw.category === "string" ? raw.category : void 0,
|
|
15596
|
-
configSchema:
|
|
15597
|
-
defaultConfig:
|
|
16030
|
+
configSchema: isRecord6(raw.configSchema) ? raw.configSchema : void 0,
|
|
16031
|
+
defaultConfig: isRecord6(raw.defaultConfig) ? raw.defaultConfig : void 0,
|
|
15598
16032
|
connection,
|
|
15599
16033
|
tools,
|
|
15600
16034
|
skills: Object.keys(skills).length > 0 ? skills : void 0,
|
|
@@ -15774,7 +16208,7 @@ var STANDARD_CONNECTION_DEFAULT_CONFIG = Object.freeze({
|
|
|
15774
16208
|
connectAll: false
|
|
15775
16209
|
});
|
|
15776
16210
|
var EMPTY_DEFAULT_CONFIG = Object.freeze({});
|
|
15777
|
-
function
|
|
16211
|
+
function isRecord7(value) {
|
|
15778
16212
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15779
16213
|
}
|
|
15780
16214
|
function runConfigFrom(exeConfig) {
|
|
@@ -15826,7 +16260,7 @@ function compileSandboxPlugin(args) {
|
|
|
15826
16260
|
maxResultBytes: t.maxResultBytes
|
|
15827
16261
|
});
|
|
15828
16262
|
if (!result.ok) return result.error ?? "sandbox plugin execution failed";
|
|
15829
|
-
const text =
|
|
16263
|
+
const text = handlerResultText(result);
|
|
15830
16264
|
const ref = t.ui ? { kind: "plugin", pluginType: manifest.type, resource: t.ui.resource, displayMode: t.ui.displayMode ?? "inline" } : null;
|
|
15831
16265
|
return ref ? appendUiFence(text, ref) : text;
|
|
15832
16266
|
},
|
|
@@ -15864,11 +16298,11 @@ async function runConnectionHandler(executor, manifest, handler, config, tenantI
|
|
|
15864
16298
|
return { ok: false, message: result.error ?? "connection test failed" };
|
|
15865
16299
|
}
|
|
15866
16300
|
const value = result.result;
|
|
15867
|
-
if (
|
|
16301
|
+
if (isRecord7(value) && typeof value.ok === "boolean" && typeof value.message === "string") {
|
|
15868
16302
|
return {
|
|
15869
16303
|
ok: value.ok,
|
|
15870
16304
|
message: value.message,
|
|
15871
|
-
details:
|
|
16305
|
+
details: isRecord7(value.details) ? value.details : void 0
|
|
15872
16306
|
};
|
|
15873
16307
|
}
|
|
15874
16308
|
return { ok: false, message: result.error ?? "connection test returned an invalid result" };
|
|
@@ -16221,13 +16655,29 @@ var TenantPluginCatalog = class {
|
|
|
16221
16655
|
|
|
16222
16656
|
// src/sandbox_plugin/sandboxPluginRuntime.ts
|
|
16223
16657
|
var catalog = null;
|
|
16658
|
+
function buildResidentRouting() {
|
|
16659
|
+
const baseUrl = process.env.MICROSANDBOX_SERVICE_BASE_URL;
|
|
16660
|
+
if (!baseUrl) return void 0;
|
|
16661
|
+
return {
|
|
16662
|
+
getDeployment: async (tenantId2, pluginType) => await getPluginDeploymentService()?.getDeployment(tenantId2, pluginType) ?? void 0,
|
|
16663
|
+
ensureRunning: async (tenantId2, pluginType) => {
|
|
16664
|
+
await getPluginDeploymentService()?.ensureRunning(tenantId2, pluginType);
|
|
16665
|
+
},
|
|
16666
|
+
baseUrl,
|
|
16667
|
+
serviceKey: process.env.MICROSANDBOX_API_KEY
|
|
16668
|
+
};
|
|
16669
|
+
}
|
|
16670
|
+
function buildExecutor(resolveSandbox) {
|
|
16671
|
+
const resident = buildResidentRouting();
|
|
16672
|
+
return new SandboxPluginExecutor({ resolveSandbox, ...resident ? { resident } : {} });
|
|
16673
|
+
}
|
|
16224
16674
|
function emptyCatalog() {
|
|
16225
16675
|
const resolveSandbox = async () => {
|
|
16226
16676
|
throw new Error("sandbox manager not configured");
|
|
16227
16677
|
};
|
|
16228
16678
|
return new TenantPluginCatalog({
|
|
16229
16679
|
resolveSandbox,
|
|
16230
|
-
executor:
|
|
16680
|
+
executor: buildExecutor(resolveSandbox),
|
|
16231
16681
|
reservedTypes: () => new Set(PluginRegistry.list())
|
|
16232
16682
|
});
|
|
16233
16683
|
}
|
|
@@ -16244,7 +16694,7 @@ function getSandboxPluginCatalog() {
|
|
|
16244
16694
|
const resolveSandbox = createSandboxResolver((config) => manager.getSandboxFromConfig(config));
|
|
16245
16695
|
catalog = new TenantPluginCatalog({
|
|
16246
16696
|
resolveSandbox,
|
|
16247
|
-
executor:
|
|
16697
|
+
executor: buildExecutor(resolveSandbox),
|
|
16248
16698
|
reservedTypes: () => new Set(PluginRegistry.list())
|
|
16249
16699
|
});
|
|
16250
16700
|
return catalog;
|
|
@@ -16391,23 +16841,23 @@ function toolName(tool68) {
|
|
|
16391
16841
|
return typeof tool68.name === "string" ? tool68.name : void 0;
|
|
16392
16842
|
}
|
|
16393
16843
|
function schemaHasConnectionKey(schema6) {
|
|
16394
|
-
const definition =
|
|
16844
|
+
const definition = isRecord8(schema6._def) ? schema6._def : void 0;
|
|
16395
16845
|
const shape = definition?.shape;
|
|
16396
16846
|
const fields = typeof shape === "function" ? shape() : shape;
|
|
16397
|
-
return
|
|
16847
|
+
return isRecord8(fields) && Object.prototype.hasOwnProperty.call(fields, "connectionKey");
|
|
16398
16848
|
}
|
|
16399
16849
|
function hasConnectionKeySchema(tool68) {
|
|
16400
|
-
if (!
|
|
16850
|
+
if (!isRecord8(tool68) || !isRecord8(tool68.schema)) return false;
|
|
16401
16851
|
if (schemaHasConnectionKey(tool68.schema)) return true;
|
|
16402
|
-
const definition =
|
|
16852
|
+
const definition = isRecord8(tool68.schema._def) ? tool68.schema._def : void 0;
|
|
16403
16853
|
if (Array.isArray(definition?.options) && definition.options.length > 0) {
|
|
16404
16854
|
return definition.options.every(
|
|
16405
|
-
(option) =>
|
|
16855
|
+
(option) => isRecord8(option) && schemaHasConnectionKey(option)
|
|
16406
16856
|
);
|
|
16407
16857
|
}
|
|
16408
16858
|
return false;
|
|
16409
16859
|
}
|
|
16410
|
-
function
|
|
16860
|
+
function isRecord8(value) {
|
|
16411
16861
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16412
16862
|
}
|
|
16413
16863
|
var CapabilityToolRegistry = class {
|
|
@@ -16499,17 +16949,17 @@ var CapabilityToolRegistry = class {
|
|
|
16499
16949
|
// src/middlewares/capabilityGuardMiddleware.ts
|
|
16500
16950
|
var import_messages4 = require("@langchain/core/messages");
|
|
16501
16951
|
var import_langchain49 = require("langchain");
|
|
16502
|
-
function
|
|
16952
|
+
function isRecord9(value) {
|
|
16503
16953
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16504
16954
|
}
|
|
16505
16955
|
function capabilityRuntimeFromRequest(request) {
|
|
16506
|
-
if (!
|
|
16956
|
+
if (!isRecord9(request) || !isRecord9(request.runtime)) return void 0;
|
|
16507
16957
|
const configurable = request.runtime.configurable;
|
|
16508
16958
|
const context = request.runtime.context;
|
|
16509
|
-
const runConfig =
|
|
16959
|
+
const runConfig = isRecord9(configurable) && isRecord9(configurable.runConfig) ? configurable.runConfig : isRecord9(context) && isRecord9(context.runConfig) ? context.runConfig : void 0;
|
|
16510
16960
|
if (!runConfig) return void 0;
|
|
16511
16961
|
const value = runConfig.capabilityRuntime;
|
|
16512
|
-
if (!
|
|
16962
|
+
if (!isRecord9(value) || typeof value.revision !== "string" || !isRecord9(value.middleware)) {
|
|
16513
16963
|
return void 0;
|
|
16514
16964
|
}
|
|
16515
16965
|
return value;
|
|
@@ -16521,10 +16971,10 @@ function hasActiveRuntimePolicy(runtime) {
|
|
|
16521
16971
|
return Object.keys(runtime.middleware).length > 0;
|
|
16522
16972
|
}
|
|
16523
16973
|
function tenantIdFromRequest(request) {
|
|
16524
|
-
if (!
|
|
16974
|
+
if (!isRecord9(request) || !isRecord9(request.runtime)) return void 0;
|
|
16525
16975
|
const configurable = request.runtime.configurable;
|
|
16526
16976
|
const context = request.runtime.context;
|
|
16527
|
-
const runConfig =
|
|
16977
|
+
const runConfig = isRecord9(configurable) && isRecord9(configurable.runConfig) ? configurable.runConfig : isRecord9(context) && isRecord9(context.runConfig) ? context.runConfig : void 0;
|
|
16528
16978
|
return runConfig && typeof runConfig.tenantId === "string" ? runConfig.tenantId : void 0;
|
|
16529
16979
|
}
|
|
16530
16980
|
async function standardConnectionAllowed(request, capabilityType, config) {
|
|
@@ -16532,8 +16982,8 @@ async function standardConnectionAllowed(request, capabilityType, config) {
|
|
|
16532
16982
|
const hasConnectAll = Object.prototype.hasOwnProperty.call(config, "connectAll");
|
|
16533
16983
|
if (!hasConnections && (!hasConnectAll || config.connectAll !== true)) return false;
|
|
16534
16984
|
const tenantId2 = tenantIdFromRequest(request);
|
|
16535
|
-
const resource =
|
|
16536
|
-
const hasConnectionKey =
|
|
16985
|
+
const resource = isRecord9(request.toolCall) && isRecord9(request.toolCall.args) ? request.toolCall.args.connectionKey : void 0;
|
|
16986
|
+
const hasConnectionKey = isRecord9(request.toolCall) && isRecord9(request.toolCall.args) && Object.prototype.hasOwnProperty.call(
|
|
16537
16987
|
request.toolCall.args,
|
|
16538
16988
|
"connectionKey"
|
|
16539
16989
|
);
|
|
@@ -16599,10 +17049,10 @@ function createCapabilityGuardMiddleware(registry4) {
|
|
|
16599
17049
|
toolName: toolName2
|
|
16600
17050
|
});
|
|
16601
17051
|
}
|
|
16602
|
-
const standardConfig =
|
|
17052
|
+
const standardConfig = isRecord9(policy.config) ? policy.config : {};
|
|
16603
17053
|
if (registry4.isStandardConnectionTool(toolName2) && !await standardConnectionAllowed(request, metadata.capabilityType, standardConfig)) {
|
|
16604
17054
|
const args2 = request.toolCall.args;
|
|
16605
|
-
const resource =
|
|
17055
|
+
const resource = isRecord9(args2) && typeof args2.connectionKey === "string" ? args2.connectionKey : void 0;
|
|
16606
17056
|
return denialMessage(request.toolCall, {
|
|
16607
17057
|
code: "RESOURCE_NOT_ALLOWED",
|
|
16608
17058
|
message: typeof resource === "string" ? `Resource '${resource}' is not allowed by the capability policy` : "Resource selector 'connectionKey' must be a permitted string",
|
|
@@ -16615,8 +17065,8 @@ function createCapabilityGuardMiddleware(registry4) {
|
|
|
16615
17065
|
const selector = metadata.resourceSelector;
|
|
16616
17066
|
const args = request.toolCall.args;
|
|
16617
17067
|
if (selector) {
|
|
16618
|
-
const resource =
|
|
16619
|
-
const config =
|
|
17068
|
+
const resource = isRecord9(args) ? args[selector.argumentField] : void 0;
|
|
17069
|
+
const config = isRecord9(policy.config) ? policy.config : {};
|
|
16620
17070
|
const hasExplicitResources = Object.prototype.hasOwnProperty.call(config, selector.configField);
|
|
16621
17071
|
const allowsAll = !hasExplicitResources && selector.allowAllField !== void 0 && config[selector.allowAllField] === true;
|
|
16622
17072
|
const allowedResources = config[selector.configField];
|
|
@@ -32391,6 +32841,7 @@ function assistantToConfig(assistant) {
|
|
|
32391
32841
|
prompt: typeof assistant.graphDefinition === "string" ? assistant.graphDefinition : JSON.stringify(assistant.graphDefinition)
|
|
32392
32842
|
};
|
|
32393
32843
|
Object.assign(config, graphDef);
|
|
32844
|
+
if (assistant.ownerUserId !== void 0) config.ownerUserId = assistant.ownerUserId;
|
|
32394
32845
|
return config;
|
|
32395
32846
|
}
|
|
32396
32847
|
var _AgentLatticeManager = class _AgentLatticeManager extends BaseLatticeManager {
|
|
@@ -34788,6 +35239,9 @@ var MicrosandboxRemoteInstance = class {
|
|
|
34788
35239
|
}
|
|
34789
35240
|
return "unknown";
|
|
34790
35241
|
}
|
|
35242
|
+
async getEndpoint(port) {
|
|
35243
|
+
return this.client.getEndpoint(this.name, port);
|
|
35244
|
+
}
|
|
34791
35245
|
/**
|
|
34792
35246
|
* Pack a sandbox folder into a sibling ZIP through the microsandbox shell.
|
|
34793
35247
|
*
|
|
@@ -34854,6 +35308,19 @@ var MicrosandboxServiceClient = class {
|
|
|
34854
35308
|
method: "GET"
|
|
34855
35309
|
});
|
|
34856
35310
|
}
|
|
35311
|
+
/**
|
|
35312
|
+
* Resolve the endpoint for a published guest port of a sandbox.
|
|
35313
|
+
*
|
|
35314
|
+
* @param name - Sandbox name
|
|
35315
|
+
* @param port - Guest port published via `ensureSandbox({ exposePorts })`
|
|
35316
|
+
* @returns The scheme-less endpoint string and optional headers
|
|
35317
|
+
*/
|
|
35318
|
+
async getEndpoint(name, port) {
|
|
35319
|
+
return this.request(
|
|
35320
|
+
`/api/sandboxes/${encodeURIComponent(name)}/endpoints/${port}`,
|
|
35321
|
+
{ method: "GET" }
|
|
35322
|
+
);
|
|
35323
|
+
}
|
|
34857
35324
|
async readFile(sandboxName, path8) {
|
|
34858
35325
|
return this.request("/api/files/read", {
|
|
34859
35326
|
method: "POST",
|
|
@@ -35009,7 +35476,9 @@ var MicrosandboxServiceClient = class {
|
|
|
35009
35476
|
}
|
|
35010
35477
|
} catch {
|
|
35011
35478
|
}
|
|
35012
|
-
|
|
35479
|
+
const error = new Error(`Microsandbox service request failed: ${detail}`);
|
|
35480
|
+
error.statusCode = response.status;
|
|
35481
|
+
throw error;
|
|
35013
35482
|
}
|
|
35014
35483
|
const payload = await response.json();
|
|
35015
35484
|
if (payload?.success === false) {
|
|
@@ -35118,9 +35587,9 @@ var MicrosandboxRemoteProvider = class {
|
|
|
35118
35587
|
}
|
|
35119
35588
|
buildEnsureInput(config) {
|
|
35120
35589
|
const defaultMicrosandboxRemoteConfig = getDefaultMicrosandboxRemoteConfig();
|
|
35121
|
-
const image = this.config.image ?? defaultMicrosandboxRemoteConfig.image;
|
|
35590
|
+
const image = config.image ?? this.config.image ?? defaultMicrosandboxRemoteConfig.image;
|
|
35122
35591
|
console.log(
|
|
35123
|
-
`[MicrosandboxRemote] buildEnsureInput image=${image} from=${this.config.image ? "providerConfig" : "defaultConfig"}`
|
|
35592
|
+
`[MicrosandboxRemote] buildEnsureInput image=${image} from=${config.image ? "runConfig" : this.config.image ? "providerConfig" : "defaultConfig"}`
|
|
35124
35593
|
);
|
|
35125
35594
|
return {
|
|
35126
35595
|
image,
|
|
@@ -35130,7 +35599,10 @@ var MicrosandboxRemoteProvider = class {
|
|
|
35130
35599
|
...this.config.env,
|
|
35131
35600
|
...buildSandboxMetadataEnv(config)
|
|
35132
35601
|
},
|
|
35133
|
-
volumes: config?.volumes ?? this.buildDefaultVolumes(config)
|
|
35602
|
+
volumes: config?.volumes ?? this.buildDefaultVolumes(config),
|
|
35603
|
+
entrypoint: config.entrypoint,
|
|
35604
|
+
timeoutSeconds: config.timeoutSeconds,
|
|
35605
|
+
exposePorts: config.exposePorts
|
|
35134
35606
|
};
|
|
35135
35607
|
}
|
|
35136
35608
|
buildDefaultVolumes(config) {
|
|
@@ -36856,6 +37328,157 @@ async function connectAllChannels(getAdapter, options = {}) {
|
|
|
36856
37328
|
}
|
|
36857
37329
|
}
|
|
36858
37330
|
}
|
|
37331
|
+
async function disconnectAllChannels(getAdapter) {
|
|
37332
|
+
const store = getStoreLattice("default", "channelInstallation").store;
|
|
37333
|
+
const installations = await store.getAllInstallations();
|
|
37334
|
+
console.log(`[disconnectAllChannels] total=${installations.length} enabled=${installations.filter((i) => i.enabled).length}`);
|
|
37335
|
+
for (const inst of installations) {
|
|
37336
|
+
if (!inst.enabled) continue;
|
|
37337
|
+
const adapter = getAdapter(inst.channel);
|
|
37338
|
+
if (!adapter?.disconnect) continue;
|
|
37339
|
+
try {
|
|
37340
|
+
await adapter.disconnect(inst.id);
|
|
37341
|
+
} catch (error) {
|
|
37342
|
+
console.error(`[disconnectAllChannels] disconnect failed: channel=${inst.channel} tenant=${inst.tenantId} id=${inst.id}`, error);
|
|
37343
|
+
}
|
|
37344
|
+
}
|
|
37345
|
+
}
|
|
37346
|
+
|
|
37347
|
+
// src/channel/ChannelConnectionLeader.ts
|
|
37348
|
+
var import_node_crypto8 = require("crypto");
|
|
37349
|
+
var LEASE_KEY = "leader:channel-connections";
|
|
37350
|
+
var ChannelConnectionLeader = class {
|
|
37351
|
+
constructor(deps) {
|
|
37352
|
+
this.holder = null;
|
|
37353
|
+
this.connected = false;
|
|
37354
|
+
this.attempted = false;
|
|
37355
|
+
this.inFlight = null;
|
|
37356
|
+
this.timer = null;
|
|
37357
|
+
this.running = false;
|
|
37358
|
+
this.stopped = false;
|
|
37359
|
+
this.deps = deps;
|
|
37360
|
+
this.ttlMs = deps.ttlMs ?? 6e4;
|
|
37361
|
+
this.retryMs = deps.retryMs ?? Math.max(5e3, Math.floor(this.ttlMs / 3));
|
|
37362
|
+
}
|
|
37363
|
+
/** Starts the ownership loop (immediate tick, then every `retryMs`). */
|
|
37364
|
+
start() {
|
|
37365
|
+
if (this.timer || this.stopped) return;
|
|
37366
|
+
void this.tickOnce();
|
|
37367
|
+
this.timer = setInterval(() => {
|
|
37368
|
+
void this.tickOnce();
|
|
37369
|
+
}, this.retryMs);
|
|
37370
|
+
this.timer.unref?.();
|
|
37371
|
+
}
|
|
37372
|
+
/**
|
|
37373
|
+
* Releases ownership (if held) and disconnects.
|
|
37374
|
+
*
|
|
37375
|
+
* @returns A promise that settles after teardown completes.
|
|
37376
|
+
* @remarks Terminal: a stopped leader does not restart.
|
|
37377
|
+
*/
|
|
37378
|
+
async stop() {
|
|
37379
|
+
this.stopped = true;
|
|
37380
|
+
if (this.timer) {
|
|
37381
|
+
clearInterval(this.timer);
|
|
37382
|
+
this.timer = null;
|
|
37383
|
+
}
|
|
37384
|
+
if (this.inFlight) await this.inFlight.catch(() => void 0);
|
|
37385
|
+
if (this.holder) {
|
|
37386
|
+
const holder = this.holder;
|
|
37387
|
+
this.holder = null;
|
|
37388
|
+
await this.deps.leaseStore.release(LEASE_KEY, holder).catch((error) => {
|
|
37389
|
+
this.deps.log?.warn("Channel leader lease release failed", {
|
|
37390
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37391
|
+
});
|
|
37392
|
+
});
|
|
37393
|
+
}
|
|
37394
|
+
await this.disconnectChannels();
|
|
37395
|
+
}
|
|
37396
|
+
/**
|
|
37397
|
+
* Runs one ownership/health step. Public for deterministic tests.
|
|
37398
|
+
*
|
|
37399
|
+
* @returns A promise that settles when this step completes.
|
|
37400
|
+
*/
|
|
37401
|
+
async tickOnce() {
|
|
37402
|
+
if (this.running || this.stopped) return;
|
|
37403
|
+
this.running = true;
|
|
37404
|
+
const run = this.runTick();
|
|
37405
|
+
this.inFlight = run;
|
|
37406
|
+
try {
|
|
37407
|
+
await run;
|
|
37408
|
+
} finally {
|
|
37409
|
+
if (this.inFlight === run) this.inFlight = null;
|
|
37410
|
+
this.running = false;
|
|
37411
|
+
}
|
|
37412
|
+
}
|
|
37413
|
+
/**
|
|
37414
|
+
* Performs one acquire/renew/step decision.
|
|
37415
|
+
*
|
|
37416
|
+
* @remarks A transient lease-store error while renewing keeps the current
|
|
37417
|
+
* holder and connections intact (retry next tick); only a confirmed
|
|
37418
|
+
* `renewed === false` triggers teardown.
|
|
37419
|
+
*/
|
|
37420
|
+
async runTick() {
|
|
37421
|
+
if (this.holder) {
|
|
37422
|
+
let renewed = false;
|
|
37423
|
+
let renewFailed = false;
|
|
37424
|
+
try {
|
|
37425
|
+
renewed = await this.deps.leaseStore.renew(LEASE_KEY, this.holder, this.ttlMs);
|
|
37426
|
+
} catch (error) {
|
|
37427
|
+
renewFailed = true;
|
|
37428
|
+
this.deps.log?.warn("Channel leader lease renew failed", {
|
|
37429
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37430
|
+
});
|
|
37431
|
+
}
|
|
37432
|
+
if (renewFailed) return;
|
|
37433
|
+
if (renewed) {
|
|
37434
|
+
if (!this.connected) await this.connectChannels();
|
|
37435
|
+
return;
|
|
37436
|
+
}
|
|
37437
|
+
await this.loseLeadership();
|
|
37438
|
+
}
|
|
37439
|
+
await this.tryAcquire();
|
|
37440
|
+
}
|
|
37441
|
+
async tryAcquire() {
|
|
37442
|
+
const holder = `${this.deps.owner}#${(0, import_node_crypto8.randomUUID)()}`;
|
|
37443
|
+
const acquired = await this.deps.leaseStore.tryAcquire(LEASE_KEY, holder, this.ttlMs).catch((error) => {
|
|
37444
|
+
this.deps.log?.warn("Channel leader lease acquire failed", {
|
|
37445
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37446
|
+
});
|
|
37447
|
+
return false;
|
|
37448
|
+
});
|
|
37449
|
+
if (!acquired) return;
|
|
37450
|
+
this.holder = holder;
|
|
37451
|
+
await this.connectChannels();
|
|
37452
|
+
}
|
|
37453
|
+
async loseLeadership() {
|
|
37454
|
+
this.holder = null;
|
|
37455
|
+
await this.disconnectChannels();
|
|
37456
|
+
}
|
|
37457
|
+
async connectChannels() {
|
|
37458
|
+
this.attempted = true;
|
|
37459
|
+
try {
|
|
37460
|
+
await this.deps.connect();
|
|
37461
|
+
this.connected = true;
|
|
37462
|
+
} catch (error) {
|
|
37463
|
+
this.connected = false;
|
|
37464
|
+
this.deps.log?.warn("Channel leader connect failed", {
|
|
37465
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37466
|
+
});
|
|
37467
|
+
}
|
|
37468
|
+
}
|
|
37469
|
+
async disconnectChannels() {
|
|
37470
|
+
if (!this.attempted) return;
|
|
37471
|
+
this.attempted = false;
|
|
37472
|
+
this.connected = false;
|
|
37473
|
+
try {
|
|
37474
|
+
await this.deps.disconnect();
|
|
37475
|
+
} catch (error) {
|
|
37476
|
+
this.deps.log?.warn("Channel leader disconnect failed", {
|
|
37477
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37478
|
+
});
|
|
37479
|
+
}
|
|
37480
|
+
}
|
|
37481
|
+
};
|
|
36859
37482
|
|
|
36860
37483
|
// src/eval_lattice/LatticeEval.ts
|
|
36861
37484
|
var import_messages7 = require("@langchain/core/messages");
|
|
@@ -38304,6 +38927,7 @@ var OutboxPoller = class {
|
|
|
38304
38927
|
};
|
|
38305
38928
|
|
|
38306
38929
|
// src/services/ProjectMutationMutex.ts
|
|
38930
|
+
var import_node_async_hooks = require("async_hooks");
|
|
38307
38931
|
var import_protocols28 = require("@axiom-lattice/protocols");
|
|
38308
38932
|
|
|
38309
38933
|
// src/services/coordinationLease.ts
|
|
@@ -38316,7 +38940,7 @@ function getCoordinationLeaseStore() {
|
|
|
38316
38940
|
}
|
|
38317
38941
|
|
|
38318
38942
|
// src/services/withLease.ts
|
|
38319
|
-
var
|
|
38943
|
+
var import_node_crypto9 = require("crypto");
|
|
38320
38944
|
var LeaseAcquisitionTimeoutError = class extends Error {
|
|
38321
38945
|
/**
|
|
38322
38946
|
* @param key Lease key that could not be acquired.
|
|
@@ -38341,7 +38965,7 @@ async function withLease(store, key5, ownerLabel, fn, options = {}) {
|
|
|
38341
38965
|
const acquireTimeoutMs = options.acquireTimeoutMs ?? 1e4;
|
|
38342
38966
|
const retryDelayMs = options.retryDelayMs ?? 50;
|
|
38343
38967
|
const heartbeatMs = options.heartbeatMs ?? Math.max(1e3, Math.floor(ttlMs / 3));
|
|
38344
|
-
const holder = `${ownerLabel}#${(0,
|
|
38968
|
+
const holder = `${ownerLabel}#${(0, import_node_crypto9.randomUUID)()}`;
|
|
38345
38969
|
const deadline = Date.now() + acquireTimeoutMs;
|
|
38346
38970
|
for (; ; ) {
|
|
38347
38971
|
if (await store.tryAcquire(key5, holder, ttlMs)) break;
|
|
@@ -38373,16 +38997,24 @@ var ProjectMutationMutex = class {
|
|
|
38373
38997
|
constructor() {
|
|
38374
38998
|
this.queues = /* @__PURE__ */ new Map();
|
|
38375
38999
|
this.active = /* @__PURE__ */ new Set();
|
|
39000
|
+
/** Project keys already held by the current async context (reentrancy). */
|
|
39001
|
+
this.held = new import_node_async_hooks.AsyncLocalStorage();
|
|
38376
39002
|
}
|
|
38377
39003
|
/** Runs one mutation after all earlier mutations for the same Tenant and Project. */
|
|
38378
39004
|
async runExclusive(scope, operation, signal) {
|
|
38379
39005
|
const key5 = projectKey(scope);
|
|
39006
|
+
const inherited = this.held.getStore();
|
|
39007
|
+
if (inherited?.has(key5)) return operation();
|
|
38380
39008
|
const leaseKey = `project-mutation:${encodeURIComponent(scope.tenantId)}:${encodeURIComponent(scope.projectId)}`;
|
|
38381
39009
|
await this.acquire(key5, signal);
|
|
39010
|
+
const held = new Set(inherited);
|
|
39011
|
+
held.add(key5);
|
|
38382
39012
|
try {
|
|
38383
|
-
|
|
38384
|
-
|
|
38385
|
-
|
|
39013
|
+
return await this.held.run(held, async () => {
|
|
39014
|
+
const store = getCoordinationLeaseStore();
|
|
39015
|
+
if (!store) return await operation();
|
|
39016
|
+
return await withLease(store, leaseKey, getReplicaId(), operation, MUTATION_LEASE_OPTIONS);
|
|
39017
|
+
});
|
|
38386
39018
|
} finally {
|
|
38387
39019
|
this.release(key5);
|
|
38388
39020
|
}
|
|
@@ -38456,6 +39088,7 @@ var INPUT_KEYS = [
|
|
|
38456
39088
|
];
|
|
38457
39089
|
var REPLY_INPUT_KEYS = ["tenantId", "membershipId", "text", "sourceRoomMessageId", "inputMessageId", "sourceId", "idempotencyKey"];
|
|
38458
39090
|
var INTERRUPT_INPUT_KEYS = ["tenantId", "membershipId", "text", "sourceRoomMessageId", "inputMessageId"];
|
|
39091
|
+
var CORRELATED_INTERRUPT_INPUT_KEYS = ["tenantId", "membershipId", "correlationKey", "text"];
|
|
38459
39092
|
var RoomAgentMessageService = class {
|
|
38460
39093
|
/**
|
|
38461
39094
|
* Creates a room writer over the durable membership and message stores.
|
|
@@ -38503,6 +39136,50 @@ var RoomAgentMessageService = class {
|
|
|
38503
39136
|
idempotencyKey: `room-interrupt:${snapshot.sourceRoomMessageId}:${snapshot.membershipId}:${sourceId}`
|
|
38504
39137
|
});
|
|
38505
39138
|
}
|
|
39139
|
+
/**
|
|
39140
|
+
* Persists a correlated HITL interrupt that has no human source message.
|
|
39141
|
+
*
|
|
39142
|
+
* @param input Explicit tenant, active membership, caller-supplied correlation key, and text.
|
|
39143
|
+
* @returns A deep clone of the canonical idempotent room message.
|
|
39144
|
+
* @remarks Business-agnostic: the caller owns the correlation key. Scope is
|
|
39145
|
+
* validated against the durable membership; no source message is required.
|
|
39146
|
+
*/
|
|
39147
|
+
postCorrelatedInterrupt(input) {
|
|
39148
|
+
const snapshot = exactStringRecord(input, CORRELATED_INTERRUPT_INPUT_KEYS, "Invalid project room interrupt input");
|
|
39149
|
+
return this.persistScoped({
|
|
39150
|
+
tenantId: snapshot.tenantId,
|
|
39151
|
+
membershipId: snapshot.membershipId,
|
|
39152
|
+
text: snapshot.text,
|
|
39153
|
+
sourceId: `interrupt:${snapshot.correlationKey}`,
|
|
39154
|
+
idempotencyKey: `room-interrupt:${snapshot.membershipId}:${snapshot.correlationKey}`
|
|
39155
|
+
});
|
|
39156
|
+
}
|
|
39157
|
+
async persistScoped(input) {
|
|
39158
|
+
const text = input.text.trim();
|
|
39159
|
+
if (text.length < 1 || text.length > 2e4) throw new Error("Invalid project room message text");
|
|
39160
|
+
const member = snapshotMembership(await this.deps.memberships.findById(input.tenantId, input.membershipId));
|
|
39161
|
+
if (!member || member.id !== input.membershipId || member.status !== "active" || member.tenantId !== input.tenantId) {
|
|
39162
|
+
throw new Error("Invalid project room membership");
|
|
39163
|
+
}
|
|
39164
|
+
const candidate = {
|
|
39165
|
+
id: (this.deps.idFactory ?? import_crypto8.randomUUID)(),
|
|
39166
|
+
tenantId: member.tenantId,
|
|
39167
|
+
workspaceId: member.workspaceId,
|
|
39168
|
+
projectId: member.projectId,
|
|
39169
|
+
roomId: member.roomId,
|
|
39170
|
+
author: { type: "bot", membershipId: member.id, assistantId: member.assistantId },
|
|
39171
|
+
content: { type: "text", text },
|
|
39172
|
+
mentions: [],
|
|
39173
|
+
source: "agent",
|
|
39174
|
+
sourceId: input.sourceId,
|
|
39175
|
+
idempotencyKey: input.idempotencyKey
|
|
39176
|
+
};
|
|
39177
|
+
const canonical = snapshotCanonicalScoped(await this.deps.messages.createIdempotent(candidate), candidate);
|
|
39178
|
+
if (!canonical) throw new Error("Invalid canonical project room message");
|
|
39179
|
+
const result = structuredClone(canonical);
|
|
39180
|
+
this.notifyCommitted(result);
|
|
39181
|
+
return result;
|
|
39182
|
+
}
|
|
38506
39183
|
async persistDerived(input) {
|
|
38507
39184
|
const source = snapshotSource(await this.deps.messages.findById(input.tenantId, input.sourceRoomMessageId));
|
|
38508
39185
|
if (!source || source.id !== input.sourceRoomMessageId || source.tenantId !== input.tenantId) throw new Error("Invalid project room message");
|
|
@@ -38658,6 +39335,27 @@ function snapshotCanonical(value, expected) {
|
|
|
38658
39335
|
createdAt
|
|
38659
39336
|
};
|
|
38660
39337
|
}
|
|
39338
|
+
function snapshotCanonicalScoped(value, expected) {
|
|
39339
|
+
const values = exactValues(value, ["id", "tenantId", "workspaceId", "projectId", "roomId", "author", "content", "mentions", "source", "sourceId", "idempotencyKey", "createdAt"], ["replyToMessageId"]);
|
|
39340
|
+
const author = exactValues(values?.author, ["type", "membershipId", "assistantId"]);
|
|
39341
|
+
const content = exactValues(values?.content, ["type", "text"]);
|
|
39342
|
+
const createdAt = date(values?.createdAt);
|
|
39343
|
+
if (!values || !createdAt || typeof values.id !== "string" || values.id.length === 0 || (0, import_protocols29.snapshotExactArray)(values.mentions)?.length !== 0 || author?.type !== "bot" || expected.author.type !== "bot" || author.membershipId !== expected.author.membershipId || author.assistantId !== expected.author.assistantId || content?.type !== "text" || content.text !== expected.content.text || values.tenantId !== expected.tenantId || values.workspaceId !== expected.workspaceId || values.projectId !== expected.projectId || values.roomId !== expected.roomId || values.source !== "agent" || values.sourceId !== expected.sourceId || values.idempotencyKey !== expected.idempotencyKey) return void 0;
|
|
39344
|
+
return {
|
|
39345
|
+
id: values.id,
|
|
39346
|
+
tenantId: expected.tenantId,
|
|
39347
|
+
workspaceId: expected.workspaceId,
|
|
39348
|
+
projectId: expected.projectId,
|
|
39349
|
+
roomId: expected.roomId,
|
|
39350
|
+
author: { type: "bot", membershipId: expected.author.membershipId, assistantId: expected.author.assistantId },
|
|
39351
|
+
content: { type: "text", text: expected.content.text },
|
|
39352
|
+
mentions: [],
|
|
39353
|
+
source: "agent",
|
|
39354
|
+
sourceId: expected.sourceId,
|
|
39355
|
+
idempotencyKey: expected.idempotencyKey,
|
|
39356
|
+
createdAt
|
|
39357
|
+
};
|
|
39358
|
+
}
|
|
38661
39359
|
|
|
38662
39360
|
// src/services/ProjectTaskOwnerPolicy.ts
|
|
38663
39361
|
var import_protocols30 = require("@axiom-lattice/protocols");
|
|
@@ -38819,11 +39517,11 @@ function snapshotDate(value) {
|
|
|
38819
39517
|
}
|
|
38820
39518
|
|
|
38821
39519
|
// src/services/TaskLifecycleService.ts
|
|
38822
|
-
var
|
|
39520
|
+
var import_node_crypto11 = require("crypto");
|
|
38823
39521
|
var import_protocols31 = require("@axiom-lattice/protocols");
|
|
38824
39522
|
|
|
38825
39523
|
// src/middlewares/taskBelief.ts
|
|
38826
|
-
var
|
|
39524
|
+
var import_node_crypto10 = require("crypto");
|
|
38827
39525
|
function buildBeliefCompletionEventKey(taskId, result, impacts) {
|
|
38828
39526
|
const canonical = JSON.stringify({
|
|
38829
39527
|
beliefImpact: impacts.map((impact) => ({
|
|
@@ -38834,7 +39532,7 @@ function buildBeliefCompletionEventKey(taskId, result, impacts) {
|
|
|
38834
39532
|
})),
|
|
38835
39533
|
result: result.trim()
|
|
38836
39534
|
});
|
|
38837
|
-
return (0,
|
|
39535
|
+
return (0, import_node_crypto10.createHash)("sha256").update(canonical).digest("hex");
|
|
38838
39536
|
}
|
|
38839
39537
|
function buildCompletionEvidenceDetail(result, impacts, eventKey) {
|
|
38840
39538
|
return { result, beliefImpact: impacts, eventKey };
|
|
@@ -39037,7 +39735,7 @@ function canonicalJson(value) {
|
|
|
39037
39735
|
return JSON.stringify(value);
|
|
39038
39736
|
}
|
|
39039
39737
|
function sha256(value) {
|
|
39040
|
-
return (0,
|
|
39738
|
+
return (0, import_node_crypto11.createHash)("sha256").update(canonicalJson(value)).digest("hex");
|
|
39041
39739
|
}
|
|
39042
39740
|
function sameEvidenceDetail(detail, taskId, result, impacts, eventKey, beliefOwner) {
|
|
39043
39741
|
const storedOwner = detail.beliefOwner;
|
|
@@ -41115,7 +41813,7 @@ function createTaskLifecycleService(deps) {
|
|
|
41115
41813
|
}
|
|
41116
41814
|
|
|
41117
41815
|
// src/services/TaskExecutionService.ts
|
|
41118
|
-
var
|
|
41816
|
+
var import_node_crypto13 = require("crypto");
|
|
41119
41817
|
var import_node_util3 = require("util");
|
|
41120
41818
|
var import_protocols32 = require("@axiom-lattice/protocols");
|
|
41121
41819
|
|
|
@@ -41438,13 +42136,13 @@ async function reconcileAllTenantTaskRuns(input) {
|
|
|
41438
42136
|
}
|
|
41439
42137
|
|
|
41440
42138
|
// src/services/taskRunLease.ts
|
|
41441
|
-
var
|
|
42139
|
+
var import_node_crypto12 = require("crypto");
|
|
41442
42140
|
function taskRunLeaseKey(taskId) {
|
|
41443
42141
|
return `task-run:${taskId}`;
|
|
41444
42142
|
}
|
|
41445
42143
|
async function tryAcquireTaskRun(input) {
|
|
41446
42144
|
const key5 = taskRunLeaseKey(input.taskId);
|
|
41447
|
-
const holder = `${input.owner}#${(0,
|
|
42145
|
+
const holder = `${input.owner}#${(0, import_node_crypto12.randomUUID)()}`;
|
|
41448
42146
|
if (!await input.store.tryAcquire(key5, holder, input.ttlMs)) return null;
|
|
41449
42147
|
const heartbeatMs = input.heartbeatMs ?? Math.max(1e3, Math.floor(input.ttlMs / 3));
|
|
41450
42148
|
let released = false;
|
|
@@ -41811,11 +42509,11 @@ function createTaskExecutionService(deps) {
|
|
|
41811
42509
|
let threadId;
|
|
41812
42510
|
let runId;
|
|
41813
42511
|
try {
|
|
41814
|
-
threadId = storedThreadId ?? (deps.createThreadId ??
|
|
42512
|
+
threadId = storedThreadId ?? (deps.createThreadId ?? import_node_crypto13.randomUUID)();
|
|
41815
42513
|
if (task.status === "interrupted" && reuseCurrentRun) {
|
|
41816
42514
|
runId = storedRunId;
|
|
41817
42515
|
} else if (task.status === "interrupted" || task.status === "failed") {
|
|
41818
|
-
runId = (deps.createRunId ??
|
|
42516
|
+
runId = (deps.createRunId ?? import_node_crypto13.randomUUID)();
|
|
41819
42517
|
}
|
|
41820
42518
|
} catch (error) {
|
|
41821
42519
|
return {
|
|
@@ -42074,11 +42772,11 @@ function createTaskExecutionService(deps) {
|
|
|
42074
42772
|
}
|
|
42075
42773
|
|
|
42076
42774
|
// src/services/leadership.ts
|
|
42077
|
-
var
|
|
42775
|
+
var import_node_crypto14 = require("crypto");
|
|
42078
42776
|
async function runAsLeader(store, name, fn, options = {}) {
|
|
42079
42777
|
const key5 = `leader:${name}`;
|
|
42080
42778
|
const owner2 = options.owner ?? getReplicaId();
|
|
42081
|
-
const holder = `${owner2}#${(0,
|
|
42779
|
+
const holder = `${owner2}#${(0, import_node_crypto14.randomUUID)()}`;
|
|
42082
42780
|
const ttlMs = options.ttlMs ?? 6e4;
|
|
42083
42781
|
if (!await store.tryAcquire(key5, holder, ttlMs)) return { ran: false };
|
|
42084
42782
|
const heartbeat = setInterval(() => {
|
|
@@ -43311,16 +44009,16 @@ function createAskUserToClarifyTool() {
|
|
|
43311
44009
|
}
|
|
43312
44010
|
|
|
43313
44011
|
// src/middlewares/askUserClarifyMiddleware.ts
|
|
43314
|
-
function
|
|
44012
|
+
function isRecord10(value) {
|
|
43315
44013
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43316
44014
|
}
|
|
43317
44015
|
function isProjectRoomRun(request) {
|
|
43318
|
-
if (!
|
|
44016
|
+
if (!isRecord10(request) || !isRecord10(request.runtime)) return false;
|
|
43319
44017
|
const runtime = request.runtime;
|
|
43320
|
-
const configurable =
|
|
43321
|
-
const context =
|
|
43322
|
-
const runConfig =
|
|
43323
|
-
return
|
|
44018
|
+
const configurable = isRecord10(runtime.configurable) ? runtime.configurable.runConfig : void 0;
|
|
44019
|
+
const context = isRecord10(runtime.context) ? runtime.context.runConfig : void 0;
|
|
44020
|
+
const runConfig = isRecord10(configurable) ? configurable : isRecord10(context) ? context : void 0;
|
|
44021
|
+
return isRecord10(runConfig?.projectRoom);
|
|
43324
44022
|
}
|
|
43325
44023
|
function createAskUserClarifyMiddleware() {
|
|
43326
44024
|
return (0, import_langchain79.createMiddleware)({
|
|
@@ -44760,15 +45458,16 @@ function executorString(value) {
|
|
|
44760
45458
|
}
|
|
44761
45459
|
async function renderAvailableExecutors(rc, tenantId2) {
|
|
44762
45460
|
const authority = classifyTaskAuthority(rc);
|
|
44763
|
-
const
|
|
44764
|
-
const
|
|
44765
|
-
const
|
|
45461
|
+
const trusted = authority.kind === "projectRoom" || authority.kind === "projectTask" ? authority.trusted : void 0;
|
|
45462
|
+
const useRoster = trusted !== void 0 && !trustedRosterExempt(trusted);
|
|
45463
|
+
const projectId = trusted ? (trusted.projectTask ?? trusted.projectRoom)?.projectId : void 0;
|
|
45464
|
+
const cacheKey = `${tenantId2}:${useRoster ? `project:${projectId ?? ""}` : "tenant"}`;
|
|
44766
45465
|
const cached2 = executorCache.get(cacheKey);
|
|
44767
45466
|
const now = Date.now();
|
|
44768
45467
|
if (cached2 && now - cached2.at < EXECUTOR_CACHE_TTL_MS) return cached2.text;
|
|
44769
45468
|
const lines = [];
|
|
44770
45469
|
try {
|
|
44771
|
-
if (
|
|
45470
|
+
if (useRoster && projectId) {
|
|
44772
45471
|
const store = getStoreLattice("default", "projectBotMembership").store;
|
|
44773
45472
|
if (typeof store.list === "function") {
|
|
44774
45473
|
const bots = await store.list(tenantId2, projectId);
|
|
@@ -44783,11 +45482,15 @@ async function renderAvailableExecutors(rc, tenantId2) {
|
|
|
44783
45482
|
}
|
|
44784
45483
|
}
|
|
44785
45484
|
} else {
|
|
45485
|
+
const exempt = trusted !== void 0 && trustedRosterExempt(trusted) ? trusted : void 0;
|
|
45486
|
+
const excluded = exempt ? await excludedRosterExecutors(exempt) : /* @__PURE__ */ new Set();
|
|
44786
45487
|
const configs = await agentLatticeManager.getAllAgentConfigsByTenant(tenantId2);
|
|
44787
45488
|
for (const config of configs.slice(0, 40)) {
|
|
44788
45489
|
const row = config;
|
|
44789
45490
|
const id = executorString(row.key);
|
|
44790
45491
|
if (!id) continue;
|
|
45492
|
+
if (row.ownerUserId !== void 0) continue;
|
|
45493
|
+
if (excluded.has(id)) continue;
|
|
44791
45494
|
const name = executorString(row.name);
|
|
44792
45495
|
const description = executorString(row.description);
|
|
44793
45496
|
lines.push(`- ${id}${name ? ` \u2014 ${name}` : ""}${description ? ` (${description.slice(0, 120)})` : ""}`);
|
|
@@ -44799,7 +45502,7 @@ async function renderAvailableExecutors(rc, tenantId2) {
|
|
|
44799
45502
|
executorCache.set(cacheKey, { at: now, text: "" });
|
|
44800
45503
|
return "";
|
|
44801
45504
|
}
|
|
44802
|
-
const header =
|
|
45505
|
+
const header = useRoster ? "## Available task executors (Project roster)" : "## Available task executors";
|
|
44803
45506
|
const text = `
|
|
44804
45507
|
|
|
44805
45508
|
${header}
|
|
@@ -44811,6 +45514,22 @@ ${lines.join("\n")}
|
|
|
44811
45514
|
executorCache.set(cacheKey, { at: now, text });
|
|
44812
45515
|
return text;
|
|
44813
45516
|
}
|
|
45517
|
+
async function excludedRosterExecutors(trusted) {
|
|
45518
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
45519
|
+
const context = trusted.projectTask ?? trusted.projectRoom;
|
|
45520
|
+
if (!context) return excluded;
|
|
45521
|
+
try {
|
|
45522
|
+
const store = getStoreLattice("default", "projectBotMembership").store;
|
|
45523
|
+
if (typeof store.list !== "function") return excluded;
|
|
45524
|
+
const rows = await store.list(context.tenantId, context.projectId);
|
|
45525
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
45526
|
+
const record = row;
|
|
45527
|
+
if (record.status !== "active" && typeof record.assistantId === "string") excluded.add(record.assistantId);
|
|
45528
|
+
}
|
|
45529
|
+
} catch {
|
|
45530
|
+
}
|
|
45531
|
+
return excluded;
|
|
45532
|
+
}
|
|
44814
45533
|
function delegatedTaskScopeError(taskId) {
|
|
44815
45534
|
return JSON.stringify({
|
|
44816
45535
|
success: false,
|
|
@@ -44860,7 +45579,7 @@ function trustedRosterScope(trusted) {
|
|
|
44860
45579
|
};
|
|
44861
45580
|
}
|
|
44862
45581
|
function trustedRosterExempt(trusted) {
|
|
44863
|
-
return trusted.projectTask?.rosterExempt === true;
|
|
45582
|
+
return trusted.projectTask?.rosterExempt === true || trusted.projectRoom?.rosterExempt === true;
|
|
44864
45583
|
}
|
|
44865
45584
|
async function requireTrustedOwner(trusted, targetAssistantId) {
|
|
44866
45585
|
const context = trusted.projectTask ?? trusted.projectRoom;
|
|
@@ -44876,6 +45595,17 @@ async function requireTrustedOwner(trusted, targetAssistantId) {
|
|
|
44876
45595
|
targetAssistantId
|
|
44877
45596
|
});
|
|
44878
45597
|
}
|
|
45598
|
+
async function rosterExemptOwnerError(tenantId2, targetAssistantId) {
|
|
45599
|
+
try {
|
|
45600
|
+
const lattice = await agentLatticeManager.getOrLoadWithTenant(tenantId2, targetAssistantId);
|
|
45601
|
+
if (lattice && lattice.config.ownerUserId === void 0) return void 0;
|
|
45602
|
+
} catch {
|
|
45603
|
+
}
|
|
45604
|
+
return projectPolicyError(new ProjectTaskOwnerPolicyError(
|
|
45605
|
+
"PROJECT_TASK_OWNER_INACTIVE",
|
|
45606
|
+
"Target Project task owner is not a shared agent of this tenant."
|
|
45607
|
+
));
|
|
45608
|
+
}
|
|
44879
45609
|
function trustedTaskContextFailure(task, trusted) {
|
|
44880
45610
|
if (task.tenantId === trusted.tenantId && task.workspaceId === trusted.workspaceId && task.projectId === trusted.projectId) return void 0;
|
|
44881
45611
|
return JSON.stringify({
|
|
@@ -45480,6 +46210,12 @@ function createTaskMiddleware(options = {}) {
|
|
|
45480
46210
|
"Target Project task owner is not active in the exact roster."
|
|
45481
46211
|
));
|
|
45482
46212
|
}
|
|
46213
|
+
} else {
|
|
46214
|
+
const ownerError = await rosterExemptOwnerError(
|
|
46215
|
+
(trustedProject.projectTask ?? trustedProject.projectRoom)?.tenantId ?? tenantId2,
|
|
46216
|
+
ownerId
|
|
46217
|
+
);
|
|
46218
|
+
if (ownerError) return ownerError;
|
|
45483
46219
|
}
|
|
45484
46220
|
}
|
|
45485
46221
|
if (!delegatedTaskId && (typeof effectiveOwnerId !== "string" || effectiveOwnerId.length === 0)) {
|
|
@@ -45541,6 +46277,12 @@ function createTaskMiddleware(options = {}) {
|
|
|
45541
46277
|
} catch (error) {
|
|
45542
46278
|
return projectPolicyError(error);
|
|
45543
46279
|
}
|
|
46280
|
+
} else {
|
|
46281
|
+
const ownerError = await rosterExemptOwnerError(
|
|
46282
|
+
(trustedProject.projectTask ?? trustedProject.projectRoom)?.tenantId ?? tenantId2,
|
|
46283
|
+
ownerId
|
|
46284
|
+
);
|
|
46285
|
+
if (ownerError) return ownerError;
|
|
45544
46286
|
}
|
|
45545
46287
|
}
|
|
45546
46288
|
return store.create({
|
|
@@ -46588,7 +47330,7 @@ function createTaskMiddleware(options = {}) {
|
|
|
46588
47330
|
}
|
|
46589
47331
|
const callerAssistantId = rc.assistant_id;
|
|
46590
47332
|
const agentId = input.agentId === "general-purpose" && callerAssistantId ? `${callerAssistantId}-general-purpose` : input.agentId;
|
|
46591
|
-
const
|
|
47333
|
+
const service3 = createTaskExecutionService({
|
|
46592
47334
|
taskStore: getTaskStore(),
|
|
46593
47335
|
workItemStore: getTaskWorkItemStore(),
|
|
46594
47336
|
resolveAgent: async (targetTenantId, agentId2) => !!await agentLatticeManager.getOrLoadWithTenant(targetTenantId, agentId2),
|
|
@@ -46613,7 +47355,7 @@ function createTaskMiddleware(options = {}) {
|
|
|
46613
47355
|
}
|
|
46614
47356
|
});
|
|
46615
47357
|
const trustedCaller = authority.kind === "projectTask" || authority.kind === "projectRoom" ? authority.trusted.projectTask ?? authority.trusted.projectRoom : void 0;
|
|
46616
|
-
return JSON.stringify(await
|
|
47358
|
+
return JSON.stringify(await service3.start({
|
|
46617
47359
|
tenantId: tenantId2,
|
|
46618
47360
|
workspaceId: rc.workspaceId,
|
|
46619
47361
|
projectId: rc.projectId,
|
|
@@ -48130,6 +48872,24 @@ function snapshotMembership3(value) {
|
|
|
48130
48872
|
function snapshotRows(value) {
|
|
48131
48873
|
return (0, import_protocols37.snapshotExactArray)(value) ?? [];
|
|
48132
48874
|
}
|
|
48875
|
+
async function rosterExemptMembershipProjection(scope) {
|
|
48876
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
48877
|
+
const roles = /* @__PURE__ */ new Map();
|
|
48878
|
+
try {
|
|
48879
|
+
const rows = snapshotRows(await getStoreLattice("default", "projectBotMembership").store.list(scope.tenantId, scope.projectId));
|
|
48880
|
+
for (const row of rows) {
|
|
48881
|
+
const member = snapshotMembership3(row);
|
|
48882
|
+
if (!member) continue;
|
|
48883
|
+
if (member.status !== "active") {
|
|
48884
|
+
excluded.add(member.assistantId);
|
|
48885
|
+
continue;
|
|
48886
|
+
}
|
|
48887
|
+
roles.set(member.assistantId, member.role);
|
|
48888
|
+
}
|
|
48889
|
+
} catch {
|
|
48890
|
+
}
|
|
48891
|
+
return { excluded, roles };
|
|
48892
|
+
}
|
|
48133
48893
|
async function hasActiveCurrentMembership(scope) {
|
|
48134
48894
|
const memberships = getStoreLattice("default", "projectBotMembership").store;
|
|
48135
48895
|
const member = snapshotMembership3(await memberships.findById(scope.tenantId, scope.membershipId));
|
|
@@ -48146,11 +48906,11 @@ function createProjectRoomMiddleware() {
|
|
|
48146
48906
|
if (!scope) return CONTEXT_REQUIRED;
|
|
48147
48907
|
const digest = (0, import_crypto10.createHash)("sha256").update(text).digest("hex");
|
|
48148
48908
|
try {
|
|
48149
|
-
const
|
|
48909
|
+
const service3 = new RoomAgentMessageService({
|
|
48150
48910
|
memberships: getStoreLattice("default", "projectBotMembership").store,
|
|
48151
48911
|
messages: getStoreLattice("default", "projectRoomMessage").store
|
|
48152
48912
|
});
|
|
48153
|
-
await
|
|
48913
|
+
await service3.post({
|
|
48154
48914
|
tenantId: scope.tenantId,
|
|
48155
48915
|
workspaceId: scope.workspaceId,
|
|
48156
48916
|
projectId: scope.projectId,
|
|
@@ -48176,6 +48936,25 @@ function createProjectRoomMiddleware() {
|
|
|
48176
48936
|
if (!scope) return CONTEXT_REQUIRED;
|
|
48177
48937
|
try {
|
|
48178
48938
|
if (!await hasActiveCurrentMembership(scope)) return CONTEXT_REQUIRED;
|
|
48939
|
+
if (scope.rosterExempt === true) {
|
|
48940
|
+
const { excluded, roles } = await rosterExemptMembershipProjection(scope);
|
|
48941
|
+
const configs = await agentLatticeManager.getAllAgentConfigsByTenant(scope.tenantId);
|
|
48942
|
+
const members2 = configs.filter((entry) => entry.ownerUserId === void 0).filter((entry) => {
|
|
48943
|
+
const key5 = entry.key;
|
|
48944
|
+
return typeof key5 === "string" && !excluded.has(key5);
|
|
48945
|
+
}).map((entry) => {
|
|
48946
|
+
const row = entry;
|
|
48947
|
+
const name = typeof row.name === "string" ? row.name : "";
|
|
48948
|
+
const assistantId = typeof row.key === "string" ? row.key : "";
|
|
48949
|
+
return {
|
|
48950
|
+
assistantId,
|
|
48951
|
+
mentionName: name,
|
|
48952
|
+
title: name,
|
|
48953
|
+
role: roles.get(assistantId) ?? "specialist"
|
|
48954
|
+
};
|
|
48955
|
+
}).filter((member) => member.assistantId !== "" && member.mentionName !== "").sort((left, right) => left.assistantId < right.assistantId ? -1 : left.assistantId > right.assistantId ? 1 : 0);
|
|
48956
|
+
return JSON.stringify({ success: true, members: members2 });
|
|
48957
|
+
}
|
|
48179
48958
|
const rows = await getStoreLattice("default", "projectBotMembership").store.list(scope.tenantId, scope.projectId);
|
|
48180
48959
|
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
48960
|
assistantId: member.assistantId,
|
|
@@ -48446,6 +49225,18 @@ On top of that, add any of three combinable classes:
|
|
|
48446
49225
|
## Combinations
|
|
48447
49226
|
A plugin is the SET of classes it needs, e.g. \`connection + tools + skills\` or \`UI + connection\`.
|
|
48448
49227
|
|
|
49228
|
+
## Scenarios \u2192 skills
|
|
49229
|
+
| Scenario | Read |
|
|
49230
|
+
|---|---|
|
|
49231
|
+
| Tool that returns/transforms data | \`sandbox-plugins-manifest\` + \`sandbox-plugins-handlers\` |
|
|
49232
|
+
| Skills-only (procedural knowledge, no tools) | \`sandbox-plugins-authoring\` (skills) |
|
|
49233
|
+
| Tool needs credentials / resource discovery | \`sandbox-plugins-connection\` |
|
|
49234
|
+
| Tool shows an interactive panel | \`sandbox-plugins-ui\` |
|
|
49235
|
+
| Tool orchestrates platform tools/agents | \`sandbox-plugins-consume-open\` |
|
|
49236
|
+
| Tool callable by external MCP clients | \`sandbox-plugins-expose\` |
|
|
49237
|
+
| Run as a long-lived resident service | \`sandbox-plugins-deploy\` |
|
|
49238
|
+
| Validate / test / debug | \`sandbox-plugins-testing\` |
|
|
49239
|
+
|
|
48449
49240
|
## Agents (not a supported authoring path)
|
|
48450
49241
|
The manifest schema accepts an \`agents\` block and tenant plugin agents are
|
|
48451
49242
|
registered, but building agents this way is not supported or designed for tenant
|
|
@@ -48656,10 +49447,38 @@ lifecycle handshake:
|
|
|
48656
49447
|
- \`ui/notifications/tool-result\` \u2192 a \`CallToolResult\` \`{ content: [{ type: "text", text }] }\` (the tool output text, with any \`mcp_app\` fence stripped).
|
|
48657
49448
|
|
|
48658
49449
|
Register the \`message\` listener **before** \`ui/initialize\` so nothing is missed.
|
|
48659
|
-
|
|
48660
|
-
|
|
48661
|
-
|
|
48662
|
-
|
|
49450
|
+
Read the payload from \`params.content[0].text\` \u2014 the notification is a
|
|
49451
|
+
\`CallToolResult\`, **not** your handler payload at the top level. The result
|
|
49452
|
+
carries **TEXT content only** \u2014 no \`structuredContent\` or \`artifact\`; parse the
|
|
49453
|
+
JSON yourself if the tool emitted JSON (it is the handler's \`result\`, so the
|
|
49454
|
+
handler must wrap it under \`result\`). To re-query or refresh, call \`tools/call\`
|
|
49455
|
+
from the View and render its response (the host does not re-push \`tool-result\`
|
|
49456
|
+
for your own calls).
|
|
49457
|
+
|
|
49458
|
+
When you call \`tools/call\` from the View, the JSON-RPC response \`result\` is the
|
|
49459
|
+
handler's own \`result\` value **directly** \u2014 it is **not wrapped in a**
|
|
49460
|
+
\`CallToolResult\` \`{ content: [...] }\` (that wrapper is only used for the host's
|
|
49461
|
+
\`tool-result\` push). Parse it as your tool's data.
|
|
49462
|
+
|
|
49463
|
+
## Push vs re-execute (key for debugging)
|
|
49464
|
+
The host pushes \`tool-result\` from the tool result **recorded when the agent ran** \u2014
|
|
49465
|
+
it does **not** re-execute the handler. The push therefore always reflects that
|
|
49466
|
+
run's output (and the handler's \`result\` at that time). A \`tools/call\` from the
|
|
49467
|
+
View, by contrast, **re-executes** the handler live every time.
|
|
49468
|
+
|
|
49469
|
+
So if the push looks stale/wrong but a fresh \`tools/call\` returns the right data,
|
|
49470
|
+
the recorded run predates your change \u2014 re-run the tool (a new agent call) to get
|
|
49471
|
+
a fresh push. Do not assume the push re-reads the handler.
|
|
49472
|
+
|
|
49473
|
+
## Self-check / debug
|
|
49474
|
+
- Log the raw frame: \`console.log(JSON.stringify(ev.data))\` in the \`message\`
|
|
49475
|
+
listener, and open the browser console inside the sandboxed frame.
|
|
49476
|
+
- If the pushed text looks like the raw handler envelope (e.g. it starts with
|
|
49477
|
+
\`{"ok":true,...\`), the handler omitted the \`result\` wrapper \u2014 the host falls
|
|
49478
|
+
back to the raw stdout instead of your payload. Fix the handler (see
|
|
49479
|
+
\`sandbox-plugins-handlers\`).
|
|
49480
|
+
- The push only happens after \`ui/initialize\` **and**
|
|
49481
|
+
\`ui/notifications/initialized\`; a listener registered later misses it.
|
|
48663
49482
|
|
|
48664
49483
|
\`templates/ui.html\` implements all of this in vanilla JS and renders both payloads
|
|
48665
49484
|
into a \`#data\` element.
|
|
@@ -48779,6 +49598,76 @@ Templates: see \`sandbox-plugins-manifest\` for the exact \`tools[]\` fields; th
|
|
|
48779
49598
|
\`templates/plugin.json\` already includes \`"expose": true\` (the tool is grantable via
|
|
48780
49599
|
an API key but not callable until a key grants it). Remove it for tools that should
|
|
48781
49600
|
not be externally grantable at all.
|
|
49601
|
+
`,
|
|
49602
|
+
resources: {}
|
|
49603
|
+
},
|
|
49604
|
+
"sandbox-plugins-consume-open": {
|
|
49605
|
+
version: "1.0.0",
|
|
49606
|
+
content: `---
|
|
49607
|
+
name: sandbox-plugins-consume-open
|
|
49608
|
+
description: Call the platform's Open MCP surface from a tenant plugin handler (discover and invoke other tools/agents). Trigger: a plugin tool must orchestrate platform capabilities, call another tool or agent, or reuse an existing capability instead of re-implementing it.
|
|
49609
|
+
---
|
|
49610
|
+
|
|
49611
|
+
# Calling the platform from a handler (Open MCP)
|
|
49612
|
+
|
|
49613
|
+
A plugin handler can call **the platform's own tools and agents** through the Open
|
|
49614
|
+
MCP endpoint. Reach for this when a tool should orchestrate existing capabilities
|
|
49615
|
+
(read the knowledge base, create a task, invoke an agent) instead of
|
|
49616
|
+
re-implementing them.
|
|
49617
|
+
|
|
49618
|
+
## What the handler gets
|
|
49619
|
+
When the gateway injects the credential, each handler process starts with:
|
|
49620
|
+
- \`API_GATEWAY_URL\` \u2014 the gateway base URL.
|
|
49621
|
+
- \`OPEN_API_TOKEN\` \u2014 a short-lived bearer **scoped to this tenant's plugin callers**.
|
|
49622
|
+
|
|
49623
|
+
If the deployment does not inject them (no \`PUBLIC_GATEWAY_URL\`), the variables are
|
|
49624
|
+
**absent** \u2014 detect that and degrade gracefully (report "platform tools
|
|
49625
|
+
unavailable") rather than crashing.
|
|
49626
|
+
|
|
49627
|
+
## Discover, then call
|
|
49628
|
+
Never hardcode the tool list: the callable set is **reflected per tenant** and
|
|
49629
|
+
changes as plugins are exposed. Call \`listTools()\` at runtime.
|
|
49630
|
+
\`\`\`js
|
|
49631
|
+
const { OpenMcpClient } = require("@axiom-lattice/open-mcp-client");
|
|
49632
|
+
// or require("/root/.agents/sdk/open-mcp-client/index.js") when provisioned there
|
|
49633
|
+
|
|
49634
|
+
async function main() {
|
|
49635
|
+
const input = process.env.PLUGIN_INPUT_FILE
|
|
49636
|
+
? JSON.parse(require("fs").readFileSync(process.env.PLUGIN_INPUT_FILE, "utf8"))
|
|
49637
|
+
: JSON.parse(process.env.PLUGIN_INPUT || "{}");
|
|
49638
|
+
|
|
49639
|
+
const client = OpenMcpClient.fromEnv(); // API_GATEWAY_URL + OPEN_API_TOKEN
|
|
49640
|
+
const tools = await client.listTools(); // exactly what this handler may call
|
|
49641
|
+
const kb = await client.callTool("kb_search", { collection: "docs", query: input.q });
|
|
49642
|
+
|
|
49643
|
+
process.stdout.write(JSON.stringify({ ok: true, result: kb }));
|
|
49644
|
+
}
|
|
49645
|
+
main().catch((err) => {
|
|
49646
|
+
process.stdout.write(JSON.stringify({ ok: false, error: String(err && err.message ? err.message : err) }));
|
|
49647
|
+
});
|
|
49648
|
+
\`\`\`
|
|
49649
|
+
|
|
49650
|
+
- Tool names are \`{domain}_{action}\` (e.g. \`kb_search\`, \`task_manage_task\`).
|
|
49651
|
+
- Args follow the tool's \`inputSchema\`; \`listTools()\` returns name/description.
|
|
49652
|
+
- Return the result through the normal handler envelope (\`{ ok: true, result }\`)
|
|
49653
|
+
so it becomes this tool's output \u2014 see \`sandbox-plugins-handlers\`.
|
|
49654
|
+
|
|
49655
|
+
## What the handler may call
|
|
49656
|
+
The token carries the conservative sandbox defaults (\`sandbox_*\`, \`kb_search\`)
|
|
49657
|
+
**plus the tenant's grantable Open catalog**: every exposed tenant/global plugin
|
|
49658
|
+
tool and the tenant's agents. A call outside that set fails (tool-not-found or
|
|
49659
|
+
\`GRANT_DENIED\`); retrying will not help.
|
|
49660
|
+
|
|
49661
|
+
## Client availability
|
|
49662
|
+
The zero-dependency \`@axiom-lattice/open-mcp-client\` must be present in the
|
|
49663
|
+
sandbox: preinstalled in the image, at \`/root/.agents/sdk/open-mcp-client\`, or
|
|
49664
|
+
installed in this run (\`npm install @axiom-lattice/open-mcp-client\`). The CLI is
|
|
49665
|
+
also available: \`npx @axiom-lattice/open-mcp-client tools list --json\`.
|
|
49666
|
+
|
|
49667
|
+
## Limits
|
|
49668
|
+
- Nested sandbox orchestration is depth-limited (5); past the cap no token is
|
|
49669
|
+
re-issued, so deep recursion fails fast.
|
|
49670
|
+
- Only call what \`listTools()\` returns, and handle a missing tool gracefully.
|
|
48782
49671
|
`,
|
|
48783
49672
|
resources: {}
|
|
48784
49673
|
},
|
|
@@ -48819,10 +49708,23 @@ for the single/first connection, or \`data.connections\` (entries of
|
|
|
48819
49708
|
\`{ key, config }\`) when the plugin selects multiple connections.
|
|
48820
49709
|
|
|
48821
49710
|
## Writing output
|
|
48822
|
-
|
|
49711
|
+
Every tool handler must print **one** JSON envelope to stdout, with your payload
|
|
49712
|
+
under \`result\`:
|
|
48823
49713
|
\`\`\`js
|
|
48824
|
-
process.stdout.write(JSON.stringify({ ok: true, result:
|
|
49714
|
+
process.stdout.write(JSON.stringify({ ok: true, result: <payload> }));
|
|
48825
49715
|
\`\`\`
|
|
49716
|
+
|
|
49717
|
+
> **The payload MUST go under \`result\`.** \`result\` may be any JSON value
|
|
49718
|
+
> (object, array, string, number).
|
|
49719
|
+
> \`\`\`js
|
|
49720
|
+
> // \u2705 correct \u2014 data under \`result\`
|
|
49721
|
+
> process.stdout.write(JSON.stringify({ ok: true, result: { tasks, count } }));
|
|
49722
|
+
>
|
|
49723
|
+
> // \u274C wrong \u2014 \`result\` missing; the host surfaces the raw stdout (this
|
|
49724
|
+
> // envelope), so callers and MCP App UIs never see \`tasks\`
|
|
49725
|
+
> process.stdout.write(JSON.stringify({ ok: true, tasks, count }));
|
|
49726
|
+
> \`\`\`
|
|
49727
|
+
|
|
48826
49728
|
Non-JSON stdout is treated as a plain-text result. To fail, either print
|
|
48827
49729
|
\`{ ok: false, error }\` or exit non-zero (stderr is not required; stdout is
|
|
48828
49730
|
captured).
|
|
@@ -48923,6 +49825,70 @@ Fall back to \`execute_code\`/\`shell\` to run \`node handlers/x.js\` manually w
|
|
|
48923
49825
|
\`PLUGIN_INPUT\` and a synthetic \`PLUGIN_CONNECTION_FILE\` to reproduce logic bugs.
|
|
48924
49826
|
A synthetic fixture only verifies plumbing \u2014 a real upstream call requires a real
|
|
48925
49827
|
\`connectionKey\` configured by a human.
|
|
49828
|
+
`,
|
|
49829
|
+
resources: {}
|
|
49830
|
+
},
|
|
49831
|
+
"sandbox-plugins-deploy": {
|
|
49832
|
+
version: "1.0.0",
|
|
49833
|
+
content: `---
|
|
49834
|
+
name: sandbox-plugins-deploy
|
|
49835
|
+
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.
|
|
49836
|
+
---
|
|
49837
|
+
|
|
49838
|
+
# Deploying a Plugin as a Resident Service
|
|
49839
|
+
|
|
49840
|
+
By default a plugin handler runs as a short-lived process on each tool call.
|
|
49841
|
+
Optionally, you can **deploy** the plugin as a **resident service**: one
|
|
49842
|
+
long-lived sandbox per plugin that serves every call. This is faster for
|
|
49843
|
+
frequent or heavy calls, but costs a running sandbox.
|
|
49844
|
+
|
|
49845
|
+
## Precondition: tests must pass first
|
|
49846
|
+
Deploy ONLY after the plugin is validated AND its tests pass:
|
|
49847
|
+
1. \`manage_plugin validate\` \u2014 no diagnostics.
|
|
49848
|
+
2. \`manage_plugin create\`/\`update\` \u2014 written and reloaded.
|
|
49849
|
+
3. \`manage_plugin test\` \u2014 every tool/connection test returns \`ok: true\`
|
|
49850
|
+
(a real \`connectionKey\` test for credentialed tools).
|
|
49851
|
+
Never deploy a plugin you have not tested: a resident service wraps the same
|
|
49852
|
+
handlers, so an untested handler fails identically \u2014 just in a service you now
|
|
49853
|
+
have to undeploy.
|
|
49854
|
+
|
|
49855
|
+
## Choose a mode
|
|
49856
|
+
- \`"onDemand"\` (default) \u2014 the service is paused when idle and resumed on the
|
|
49857
|
+
next call. Best for plugins used occasionally. Lower steady cost.
|
|
49858
|
+
- \`"alwaysOn"\` \u2014 the service stays resident and is restarted if it becomes
|
|
49859
|
+
unhealthy. Best for plugins on a hot path or with expensive startup.
|
|
49860
|
+
|
|
49861
|
+
## Deploy
|
|
49862
|
+
\`\`\`jsonc
|
|
49863
|
+
{ "action": "deploy", "type": "my-plugin", "mode": "alwaysOn" }
|
|
49864
|
+
\`\`\`
|
|
49865
|
+
Returns \`{ deployed, status, endpoint, mode }\`. \`status\` is \`"running"\` when
|
|
49866
|
+
the service answered its health check. On failure you get
|
|
49867
|
+
\`{ deployed: false, error }\` \u2014 report it, do not treat it as success.
|
|
49868
|
+
Deploying again is idempotent: it reuses the same service.
|
|
49869
|
+
|
|
49870
|
+
## Check status
|
|
49871
|
+
\`\`\`jsonc
|
|
49872
|
+
{ "action": "status", "type": "my-plugin" }
|
|
49873
|
+
\`\`\`
|
|
49874
|
+
Returns \`{ status, endpoint?, mode?, lastHealthAt?, lastError? }\`. \`"absent"\`
|
|
49875
|
+
means no deployment exists; \`"failed"\` (with \`lastError\`) means the service is
|
|
49876
|
+
down. Inspect this before reporting the plugin as serving.
|
|
49877
|
+
|
|
49878
|
+
## Undeploy
|
|
49879
|
+
\`\`\`jsonc
|
|
49880
|
+
{ "action": "undeploy", "type": "my-plugin" }
|
|
49881
|
+
\`\`\`
|
|
49882
|
+
Returns \`{ undeployed }\`. Undeploy removes the resident sandbox and its
|
|
49883
|
+
deployment record; the plugin files and manifest are untouched, so tool calls
|
|
49884
|
+
fall back to the short-process path. Use it to stop paying for a service you no
|
|
49885
|
+
longer need.
|
|
49886
|
+
|
|
49887
|
+
## When resident services are unavailable
|
|
49888
|
+
If the environment has no deployment service registered, deploy/undeploy/status
|
|
49889
|
+
return a diagnostic (\`{ deployed: false, error }\` etc.) instead of throwing.
|
|
49890
|
+
That is not a plugin defect: the plugin still works through the default
|
|
49891
|
+
short-process path, so report it as not-deployed rather than broken.
|
|
48926
49892
|
`,
|
|
48927
49893
|
resources: {}
|
|
48928
49894
|
}
|
|
@@ -48944,6 +49910,8 @@ Follow the authoring skill's "Decide first" steps to confirm the goal
|
|
|
48944
49910
|
(inputs/outputs). If the UI class applies, load "sandbox-plugins-ui" before
|
|
48945
49911
|
authoring it. If the tool must be callable by external MCP clients via an API key,
|
|
48946
49912
|
load "sandbox-plugins-expose" and follow it (\`expose: true\` + key grants).
|
|
49913
|
+
If a tool must call the platform's own tools/agents from its handler, load
|
|
49914
|
+
"sandbox-plugins-consume-open".
|
|
48947
49915
|
|
|
48948
49916
|
Plan with persistent Tasks: MANDATORY until you load "task-tracking" with the
|
|
48949
49917
|
\`skill\` tool, call neither \`manage_task\` nor any other task operation. Use
|
|
@@ -48959,10 +49927,197 @@ the connector and give you its key. Only after a real \`connectionKey\` test
|
|
|
48959
49927
|
passes may you call the tool verified; without it, report functionality as
|
|
48960
49928
|
unverified (missing credentials), not as passing. Use \`shell_exec\` (the tool
|
|
48961
49929
|
provided by the \`code_eval\` middleware) to run \`node handlers/x.js\` for manual
|
|
48962
|
-
checks when reproducing a failure
|
|
49930
|
+
checks when reproducing a failure.
|
|
49931
|
+
|
|
49932
|
+
After the plugin passes \`validate\` and its tests, deploy it as a resident
|
|
49933
|
+
service: load "sandbox-plugins-deploy" and follow it. Choose \`onDemand\`
|
|
49934
|
+
(default) or \`alwaysOn\` for hot paths, call \`manage_plugin deploy\` with the
|
|
49935
|
+
chosen \`mode\`, then confirm with \`manage_plugin status\` (\`running\`). Only claim
|
|
49936
|
+
the plugin is deployed and serving once \`status\` reports \`running\`; if the
|
|
49937
|
+
environment reports no deployment service, say so and treat deployment as
|
|
49938
|
+
unavailable \u2014 do NOT claim success. \`undeploy\` removes the service without
|
|
49939
|
+
touching the plugin files.`;
|
|
49940
|
+
|
|
49941
|
+
// src/middlewares/sandboxPluginsUi.ts
|
|
49942
|
+
var SANDBOX_PLUGINS_UI_RESOURCE = "ui://sandbox-plugins/app";
|
|
49943
|
+
var SANDBOX_PLUGINS_APP_HTML = `<!doctype html>
|
|
49944
|
+
<html lang="en">
|
|
49945
|
+
<head>
|
|
49946
|
+
<meta charset="utf-8" />
|
|
49947
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
49948
|
+
<title>Plugin Deployments</title>
|
|
49949
|
+
<style>
|
|
49950
|
+
:root { color-scheme: light dark; }
|
|
49951
|
+
* { box-sizing: border-box; }
|
|
49952
|
+
body {
|
|
49953
|
+
margin: 0;
|
|
49954
|
+
padding: 14px 16px;
|
|
49955
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
49956
|
+
font-size: 13px;
|
|
49957
|
+
color: #1f2430;
|
|
49958
|
+
background: transparent;
|
|
49959
|
+
}
|
|
49960
|
+
h1 { font-size: 14px; font-weight: 600; margin: 0 0 2px; }
|
|
49961
|
+
.subtitle { color: #6b7280; font-size: 12px; margin: 0 0 12px; }
|
|
49962
|
+
table { width: 100%; border-collapse: collapse; }
|
|
49963
|
+
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid #eceef2; vertical-align: top; }
|
|
49964
|
+
th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280; font-weight: 600; }
|
|
49965
|
+
td.type { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; white-space: nowrap; }
|
|
49966
|
+
td.mode { white-space: nowrap; color: #4b5563; }
|
|
49967
|
+
td.detail { color: #4b5563; word-break: break-word; max-width: 280px; }
|
|
49968
|
+
.muted { color: #9ca3af; }
|
|
49969
|
+
.status { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
|
|
49970
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: #9ca3af; }
|
|
49971
|
+
.dot.running { background: #22c55e; }
|
|
49972
|
+
.dot.failed { background: #ef4444; }
|
|
49973
|
+
.dot.requested, .dot.provisioning, .dot.stopping { background: #f59e0b; }
|
|
49974
|
+
.dot.paused, .dot.stopped { background: #94a3b8; }
|
|
49975
|
+
.state { color: #6b7280; padding: 14px 2px; }
|
|
49976
|
+
.error { margin: 0 0 10px; padding: 8px 10px; border-radius: 8px; background: #fef2f2; color: #b91c1c; }
|
|
49977
|
+
</style>
|
|
49978
|
+
</head>
|
|
49979
|
+
<body>
|
|
49980
|
+
<h1>Plugin Deployments</h1>
|
|
49981
|
+
<p class="subtitle">Resident services deployed for this tenant. Read-only.</p>
|
|
49982
|
+
<div id="root"><p class="state">Loading\u2026</p></div>
|
|
49983
|
+
<script>
|
|
49984
|
+
(function () {
|
|
49985
|
+
var root = document.getElementById("root");
|
|
49986
|
+
|
|
49987
|
+
function post(message) {
|
|
49988
|
+
window.parent.postMessage(message, "*");
|
|
49989
|
+
}
|
|
49990
|
+
function notify(method, params) {
|
|
49991
|
+
post({ jsonrpc: "2.0", method: method, params: params });
|
|
49992
|
+
}
|
|
49993
|
+
var nextId = 1;
|
|
49994
|
+
function rpc(method, params) {
|
|
49995
|
+
var id = nextId++;
|
|
49996
|
+
return new Promise(function (resolve, reject) {
|
|
49997
|
+
function onMessage(event) {
|
|
49998
|
+
var data = event.data;
|
|
49999
|
+
if (!data || data.id !== id) return;
|
|
50000
|
+
window.removeEventListener("message", onMessage);
|
|
50001
|
+
if (data.error) reject(new Error((data.error && data.error.message) || "rpc error"));
|
|
50002
|
+
else resolve(data.result);
|
|
50003
|
+
}
|
|
50004
|
+
window.addEventListener("message", onMessage);
|
|
50005
|
+
post({ jsonrpc: "2.0", id: id, method: method, params: params });
|
|
50006
|
+
});
|
|
50007
|
+
}
|
|
50008
|
+
|
|
50009
|
+
function sendSize() {
|
|
50010
|
+
var width = Math.ceil(document.documentElement.scrollWidth || document.body.scrollWidth);
|
|
50011
|
+
var height = Math.ceil(document.documentElement.scrollHeight || document.body.scrollHeight);
|
|
50012
|
+
notify("ui/notifications/size-changed", { width: width, height: height });
|
|
50013
|
+
}
|
|
50014
|
+
|
|
50015
|
+
function esc(value) {
|
|
50016
|
+
return String(value === null || value === undefined ? "" : value).replace(/[&<>"']/g, function (ch) {
|
|
50017
|
+
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch];
|
|
50018
|
+
});
|
|
50019
|
+
}
|
|
50020
|
+
|
|
50021
|
+
function statusClass(status) {
|
|
50022
|
+
switch (status) {
|
|
50023
|
+
case "running": return "running";
|
|
50024
|
+
case "failed": return "failed";
|
|
50025
|
+
case "requested":
|
|
50026
|
+
case "provisioning":
|
|
50027
|
+
case "stopping": return "requested";
|
|
50028
|
+
case "paused":
|
|
50029
|
+
case "stopped": return "paused";
|
|
50030
|
+
default: return "";
|
|
50031
|
+
}
|
|
50032
|
+
}
|
|
50033
|
+
|
|
50034
|
+
function healthCell(item) {
|
|
50035
|
+
if (item.lastError) return '<span style="color:#b91c1c">' + esc(item.lastError) + "</span>";
|
|
50036
|
+
if (item.lastHealthAt) return '<span class="muted">' + esc(item.lastHealthAt) + "</span>";
|
|
50037
|
+
return '<span class="muted">\u2014</span>';
|
|
50038
|
+
}
|
|
50039
|
+
|
|
50040
|
+
function render(payload) {
|
|
50041
|
+
var deployments = payload && Array.isArray(payload.deployments) ? payload.deployments : [];
|
|
50042
|
+
var error = payload && payload.error ? String(payload.error) : "";
|
|
50043
|
+
var html = "";
|
|
50044
|
+
if (error) html += '<div class="error">' + esc(error) + "</div>";
|
|
50045
|
+
if (deployments.length === 0) {
|
|
50046
|
+
html += '<p class="state">' + (error ? "No deployments available." : "No resident plugin deployments.") + "</p>";
|
|
50047
|
+
root.innerHTML = html;
|
|
50048
|
+
sendSize();
|
|
50049
|
+
return;
|
|
50050
|
+
}
|
|
50051
|
+
html += "<table><thead><tr><th>Plugin</th><th>Status</th><th>Mode</th><th>Endpoint</th><th>Health</th></tr></thead><tbody>";
|
|
50052
|
+
deployments.forEach(function (item) {
|
|
50053
|
+
var status = item && item.status ? String(item.status) : "unknown";
|
|
50054
|
+
html += "<tr>";
|
|
50055
|
+
html += '<td class="type">' + esc(item && item.pluginType) + "</td>";
|
|
50056
|
+
html += '<td><span class="status"><span class="dot ' + statusClass(status) + '"></span>' + esc(status) + "</span></td>";
|
|
50057
|
+
html += '<td class="mode">' + esc(item && item.mode) + "</td>";
|
|
50058
|
+
html += '<td class="detail">' + (item && item.endpoint ? esc(item.endpoint) : '<span class="muted">\u2014</span>') + "</td>";
|
|
50059
|
+
html += '<td class="detail">' + healthCell(item || {}) + "</td>";
|
|
50060
|
+
html += "</tr>";
|
|
50061
|
+
});
|
|
50062
|
+
html += "</tbody></table>";
|
|
50063
|
+
root.innerHTML = html;
|
|
50064
|
+
sendSize();
|
|
50065
|
+
}
|
|
50066
|
+
|
|
50067
|
+
function readResult(params) {
|
|
50068
|
+
if (!params || typeof params !== "object") return null;
|
|
50069
|
+
if (params.structuredContent && typeof params.structuredContent === "object") {
|
|
50070
|
+
return params.structuredContent;
|
|
50071
|
+
}
|
|
50072
|
+
var content = params.content;
|
|
50073
|
+
var text = content && content[0] && typeof content[0].text === "string" ? content[0].text : "";
|
|
50074
|
+
if (!text) return null;
|
|
50075
|
+
try { return JSON.parse(text); } catch (err) { return null; }
|
|
50076
|
+
}
|
|
50077
|
+
|
|
50078
|
+
// 1) Register the tool-result listener BEFORE initializing so the data
|
|
50079
|
+
// the host pushes right after the handshake is never missed.
|
|
50080
|
+
window.addEventListener("message", function (event) {
|
|
50081
|
+
var data = event.data;
|
|
50082
|
+
if (!data || typeof data !== "object" || typeof data.method !== "string") return;
|
|
50083
|
+
if (data.method === "ui/notifications/tool-result") {
|
|
50084
|
+
render(readResult(data.params));
|
|
50085
|
+
}
|
|
50086
|
+
});
|
|
50087
|
+
|
|
50088
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
50089
|
+
new ResizeObserver(sendSize).observe(document.documentElement);
|
|
50090
|
+
} else {
|
|
50091
|
+
window.addEventListener("resize", sendSize);
|
|
50092
|
+
}
|
|
50093
|
+
|
|
50094
|
+
// 2) MCP Apps lifecycle: initialize, then signal readiness. The host
|
|
50095
|
+
// pushes tool data only after this completes.
|
|
50096
|
+
rpc("ui/initialize", {
|
|
50097
|
+
protocolVersion: "2025-06-18",
|
|
50098
|
+
capabilities: {},
|
|
50099
|
+
clientInfo: { name: "sandbox-plugins-ui", version: "1.0.0" },
|
|
50100
|
+
appCapabilities: { availableDisplayModes: ["inline"] }
|
|
50101
|
+
}).then(function () {
|
|
50102
|
+
notify("ui/notifications/initialized", {});
|
|
50103
|
+
sendSize();
|
|
50104
|
+
}).catch(function (err) {
|
|
50105
|
+
root.innerHTML = '<p class="state">Failed to initialize: ' + esc((err && err.message) || err) + "</p>";
|
|
50106
|
+
sendSize();
|
|
50107
|
+
});
|
|
50108
|
+
})();
|
|
50109
|
+
</script>
|
|
50110
|
+
</body>
|
|
50111
|
+
</html>
|
|
50112
|
+
`;
|
|
48963
50113
|
|
|
48964
50114
|
// src/middlewares/sandboxPluginsMiddleware.ts
|
|
48965
|
-
var
|
|
50115
|
+
var SANDBOX_PLUGINS_UI_REF = {
|
|
50116
|
+
kind: "plugin",
|
|
50117
|
+
pluginType: "sandbox-plugins",
|
|
50118
|
+
resource: SANDBOX_PLUGINS_UI_RESOURCE,
|
|
50119
|
+
displayMode: "inline"
|
|
50120
|
+
};
|
|
48966
50121
|
function tenantIdFrom(exeConfig) {
|
|
48967
50122
|
const c = exeConfig;
|
|
48968
50123
|
return c?.configurable?.runConfig?.tenantId ?? "default";
|
|
@@ -48979,6 +50134,15 @@ function collectSecretValues(connection, fields) {
|
|
|
48979
50134
|
function errorText(err) {
|
|
48980
50135
|
return err instanceof Error ? err.message : String(err);
|
|
48981
50136
|
}
|
|
50137
|
+
function deploymentUnavailable(action) {
|
|
50138
|
+
const error = `plugin deployment service is not configured; cannot ${action}. Resident plugin services are unavailable in this environment.`;
|
|
50139
|
+
if (action === "undeploy") return JSON.stringify({ undeployed: false, error });
|
|
50140
|
+
if (action === "status") return JSON.stringify({ status: "unavailable", error });
|
|
50141
|
+
return JSON.stringify({ deployed: false, error });
|
|
50142
|
+
}
|
|
50143
|
+
function deploymentsResult(payload) {
|
|
50144
|
+
return appendUiFence(JSON.stringify(payload), SANDBOX_PLUGINS_UI_REF);
|
|
50145
|
+
}
|
|
48982
50146
|
function redactString(value, secrets) {
|
|
48983
50147
|
let out = value;
|
|
48984
50148
|
for (const secret of secrets) out = out.split(secret).join("[redacted]");
|
|
@@ -48999,10 +50163,24 @@ function redactSecretValues(payload, secrets) {
|
|
|
48999
50163
|
return walk(payload);
|
|
49000
50164
|
}
|
|
49001
50165
|
var managePluginSchema = import_zod71.z.object({
|
|
49002
|
-
action: import_zod71.z.enum([
|
|
50166
|
+
action: import_zod71.z.enum([
|
|
50167
|
+
"list",
|
|
50168
|
+
"get",
|
|
50169
|
+
"validate",
|
|
50170
|
+
"create",
|
|
50171
|
+
"update",
|
|
50172
|
+
"delete",
|
|
50173
|
+
"reload",
|
|
50174
|
+
"test",
|
|
50175
|
+
"deploy",
|
|
50176
|
+
"undeploy",
|
|
50177
|
+
"status",
|
|
50178
|
+
"deployments"
|
|
50179
|
+
]),
|
|
49003
50180
|
type: import_zod71.z.string().optional(),
|
|
49004
50181
|
manifest: import_zod71.z.record(import_zod71.z.unknown()).optional(),
|
|
49005
50182
|
handlers: import_zod71.z.record(import_zod71.z.string()).optional(),
|
|
50183
|
+
mode: import_zod71.z.enum(["onDemand", "alwaysOn"]).optional(),
|
|
49006
50184
|
target: import_zod71.z.object({
|
|
49007
50185
|
kind: import_zod71.z.enum(["tool", "connection-test", "connection-discover"]),
|
|
49008
50186
|
name: import_zod71.z.string().optional(),
|
|
@@ -49015,9 +50193,18 @@ var sandboxPluginsPlugin = {
|
|
|
49015
50193
|
type: "sandbox-plugins",
|
|
49016
50194
|
category: "execution",
|
|
49017
50195
|
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.",
|
|
50196
|
+
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
50197
|
recommends: ["skill"],
|
|
49020
|
-
tools: [
|
|
50198
|
+
tools: [
|
|
50199
|
+
{
|
|
50200
|
+
name: "manage_plugin",
|
|
50201
|
+
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.",
|
|
50202
|
+
ui: { resource: SANDBOX_PLUGINS_UI_RESOURCE, displayMode: "inline" }
|
|
50203
|
+
}
|
|
50204
|
+
],
|
|
50205
|
+
uiResources: {
|
|
50206
|
+
[SANDBOX_PLUGINS_UI_RESOURCE]: { html: SANDBOX_PLUGINS_APP_HTML }
|
|
50207
|
+
},
|
|
49021
50208
|
configSchema: { type: "object", properties: {} },
|
|
49022
50209
|
defaultConfig: {}
|
|
49023
50210
|
},
|
|
@@ -49038,7 +50225,7 @@ var sandboxPluginsPlugin = {
|
|
|
49038
50225
|
id: "sandbox-plugins",
|
|
49039
50226
|
type: "sandbox-plugins",
|
|
49040
50227
|
name: "Sandbox Plugins",
|
|
49041
|
-
description: "Author and manage tenant plugins (list/validate/create/update/delete/reload/test)",
|
|
50228
|
+
description: "Author and manage tenant plugins (list/validate/create/update/delete/reload/test/deploy/undeploy/status)",
|
|
49042
50229
|
enabled: true,
|
|
49043
50230
|
config: {}
|
|
49044
50231
|
},
|
|
@@ -49046,9 +50233,9 @@ var sandboxPluginsPlugin = {
|
|
|
49046
50233
|
id: "skill",
|
|
49047
50234
|
type: "skill",
|
|
49048
50235
|
name: "Skill",
|
|
49049
|
-
description: "Load the sandbox-plugins authoring, patterns, manifest, handler, connection, UI, and testing skills",
|
|
50236
|
+
description: "Load any available skill, including the sandbox-plugins authoring, patterns, manifest, handler, connection, UI, and testing skills",
|
|
49050
50237
|
enabled: true,
|
|
49051
|
-
config: { readAll:
|
|
50238
|
+
config: { readAll: true }
|
|
49052
50239
|
},
|
|
49053
50240
|
{
|
|
49054
50241
|
id: "task",
|
|
@@ -49229,18 +50416,79 @@ var sandboxPluginsPlugin = {
|
|
|
49229
50416
|
assistantId: typeof runCfg.assistant_id === "string" ? runCfg.assistant_id : void 0
|
|
49230
50417
|
}
|
|
49231
50418
|
});
|
|
49232
|
-
return
|
|
50419
|
+
return handlerResultText(redactSecretValues(result, secrets));
|
|
49233
50420
|
} catch (err) {
|
|
49234
50421
|
return JSON.stringify(redactSecretValues({ ok: false, error: errorText(err) }, secrets));
|
|
49235
50422
|
}
|
|
49236
50423
|
}
|
|
50424
|
+
case "deploy": {
|
|
50425
|
+
if (!input.type) return "type is required";
|
|
50426
|
+
const service3 = getPluginDeploymentService();
|
|
50427
|
+
if (!service3) return deploymentUnavailable("deploy");
|
|
50428
|
+
try {
|
|
50429
|
+
const result = await service3.deploy(tenantId2, input.type, {
|
|
50430
|
+
...input.mode === void 0 ? {} : { mode: input.mode }
|
|
50431
|
+
});
|
|
50432
|
+
return JSON.stringify({
|
|
50433
|
+
deployed: true,
|
|
50434
|
+
status: result.status,
|
|
50435
|
+
endpoint: result.endpoint,
|
|
50436
|
+
mode: result.mode
|
|
50437
|
+
});
|
|
50438
|
+
} catch (err) {
|
|
50439
|
+
return JSON.stringify({ deployed: false, error: errorText(err) });
|
|
50440
|
+
}
|
|
50441
|
+
}
|
|
50442
|
+
case "undeploy": {
|
|
50443
|
+
if (!input.type) return "type is required";
|
|
50444
|
+
const service3 = getPluginDeploymentService();
|
|
50445
|
+
if (!service3) return deploymentUnavailable("undeploy");
|
|
50446
|
+
try {
|
|
50447
|
+
const result = await service3.undeploy(tenantId2, input.type);
|
|
50448
|
+
return JSON.stringify({ undeployed: result.undeployed });
|
|
50449
|
+
} catch (err) {
|
|
50450
|
+
return JSON.stringify({ undeployed: false, error: errorText(err) });
|
|
50451
|
+
}
|
|
50452
|
+
}
|
|
50453
|
+
case "status": {
|
|
50454
|
+
if (!input.type) return "type is required";
|
|
50455
|
+
const service3 = getPluginDeploymentService();
|
|
50456
|
+
if (!service3) return deploymentUnavailable("status");
|
|
50457
|
+
try {
|
|
50458
|
+
const result = await service3.status(tenantId2, input.type);
|
|
50459
|
+
return JSON.stringify({
|
|
50460
|
+
status: result.status,
|
|
50461
|
+
endpoint: result.endpoint,
|
|
50462
|
+
mode: result.mode,
|
|
50463
|
+
lastHealthAt: result.lastHealthAt,
|
|
50464
|
+
lastError: result.lastError
|
|
50465
|
+
});
|
|
50466
|
+
} catch (err) {
|
|
50467
|
+
return JSON.stringify({ status: "unavailable", error: errorText(err) });
|
|
50468
|
+
}
|
|
50469
|
+
}
|
|
50470
|
+
case "deployments": {
|
|
50471
|
+
const service3 = getPluginDeploymentService();
|
|
50472
|
+
if (!service3) {
|
|
50473
|
+
return deploymentsResult({
|
|
50474
|
+
deployments: [],
|
|
50475
|
+
error: "plugin deployment service is not configured; cannot list deployments. Resident plugin services are unavailable in this environment."
|
|
50476
|
+
});
|
|
50477
|
+
}
|
|
50478
|
+
try {
|
|
50479
|
+
const deployments = await service3.list(tenantId2);
|
|
50480
|
+
return deploymentsResult({ deployments });
|
|
50481
|
+
} catch (err) {
|
|
50482
|
+
return deploymentsResult({ deployments: [], error: errorText(err) });
|
|
50483
|
+
}
|
|
50484
|
+
}
|
|
49237
50485
|
default:
|
|
49238
50486
|
return `unsupported action: ${input.action}`;
|
|
49239
50487
|
}
|
|
49240
50488
|
},
|
|
49241
50489
|
{
|
|
49242
50490
|
name: "manage_plugin",
|
|
49243
|
-
description: "Author and manage tenant sandbox plugins (list/get/validate/create/update/delete/reload/test).",
|
|
50491
|
+
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
50492
|
schema: managePluginSchema
|
|
49245
50493
|
}
|
|
49246
50494
|
)
|
|
@@ -49295,6 +50543,526 @@ function buildBundleFromDist(distDir, version) {
|
|
|
49295
50543
|
};
|
|
49296
50544
|
}
|
|
49297
50545
|
|
|
50546
|
+
// src/sandbox_plugin/PluginDeploymentRuntimeRegistry.ts
|
|
50547
|
+
var PluginDeploymentRuntimeRegistry = class {
|
|
50548
|
+
/**
|
|
50549
|
+
* @param options - Optional injectable clock for deterministic health timestamps.
|
|
50550
|
+
*/
|
|
50551
|
+
constructor(options = {}) {
|
|
50552
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
50553
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
50554
|
+
this.now = options.now ?? Date.now;
|
|
50555
|
+
}
|
|
50556
|
+
/**
|
|
50557
|
+
* Cache the resolved proxy endpoint for a deployment.
|
|
50558
|
+
*
|
|
50559
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
50560
|
+
* @param endpoint - Relative proxy path, e.g. `/api/sandboxes/plugin-abc/proxy/8080`.
|
|
50561
|
+
*/
|
|
50562
|
+
setEndpoint(id, endpoint) {
|
|
50563
|
+
this.entry(id).endpoint = endpoint;
|
|
50564
|
+
}
|
|
50565
|
+
/**
|
|
50566
|
+
* Read the cached endpoint for a deployment.
|
|
50567
|
+
*
|
|
50568
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
50569
|
+
* @returns The cached endpoint, or `undefined` when none is known.
|
|
50570
|
+
*/
|
|
50571
|
+
getEndpoint(id) {
|
|
50572
|
+
return this.entries.get(id)?.endpoint;
|
|
50573
|
+
}
|
|
50574
|
+
/**
|
|
50575
|
+
* Record a successful health observation and clear any prior error.
|
|
50576
|
+
*
|
|
50577
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
50578
|
+
*/
|
|
50579
|
+
markHealthy(id) {
|
|
50580
|
+
const entry = this.entry(id);
|
|
50581
|
+
entry.lastHealthAt = this.now();
|
|
50582
|
+
delete entry.lastError;
|
|
50583
|
+
}
|
|
50584
|
+
/**
|
|
50585
|
+
* Record a failed health observation.
|
|
50586
|
+
*
|
|
50587
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
50588
|
+
* @param error - Human-readable failure description.
|
|
50589
|
+
*/
|
|
50590
|
+
markUnhealthy(id, error) {
|
|
50591
|
+
const entry = this.entry(id);
|
|
50592
|
+
entry.lastHealthAt = this.now();
|
|
50593
|
+
entry.lastError = error;
|
|
50594
|
+
}
|
|
50595
|
+
/**
|
|
50596
|
+
* Read the last observed health for a deployment.
|
|
50597
|
+
*
|
|
50598
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
50599
|
+
* @returns The cached health, or `undefined` when the id is unknown.
|
|
50600
|
+
*/
|
|
50601
|
+
getHealth(id) {
|
|
50602
|
+
const entry = this.entries.get(id);
|
|
50603
|
+
if (!entry) return void 0;
|
|
50604
|
+
const health = {};
|
|
50605
|
+
if (entry.lastHealthAt !== void 0) health.lastHealthAt = entry.lastHealthAt;
|
|
50606
|
+
if (entry.lastError !== void 0) health.lastError = entry.lastError;
|
|
50607
|
+
return health;
|
|
50608
|
+
}
|
|
50609
|
+
/**
|
|
50610
|
+
* Run a cold start/resume factory at most once per id at a time.
|
|
50611
|
+
*
|
|
50612
|
+
* Concurrent calls for the same id return the same in-flight promise and run
|
|
50613
|
+
* `factory` exactly once. The in-flight entry is cleared when the factory
|
|
50614
|
+
* settles, so a later call runs it again (retry after failure is allowed).
|
|
50615
|
+
*
|
|
50616
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
50617
|
+
* @param factory - Idempotent start/resume operation for the resident service.
|
|
50618
|
+
* @returns A promise that settles with the factory outcome.
|
|
50619
|
+
*/
|
|
50620
|
+
ensureRunning(id, factory) {
|
|
50621
|
+
const existing = this.inFlight.get(id);
|
|
50622
|
+
if (existing) return existing;
|
|
50623
|
+
let started;
|
|
50624
|
+
try {
|
|
50625
|
+
started = factory();
|
|
50626
|
+
} catch (error) {
|
|
50627
|
+
started = Promise.reject(error);
|
|
50628
|
+
}
|
|
50629
|
+
const tracked = started.finally(() => {
|
|
50630
|
+
if (this.inFlight.get(id) === tracked) {
|
|
50631
|
+
this.inFlight.delete(id);
|
|
50632
|
+
}
|
|
50633
|
+
});
|
|
50634
|
+
this.inFlight.set(id, tracked);
|
|
50635
|
+
return tracked;
|
|
50636
|
+
}
|
|
50637
|
+
/**
|
|
50638
|
+
* Drop cached endpoint and health for one deployment.
|
|
50639
|
+
*
|
|
50640
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
50641
|
+
*/
|
|
50642
|
+
clear(id) {
|
|
50643
|
+
this.entries.delete(id);
|
|
50644
|
+
}
|
|
50645
|
+
/** Drop all cached endpoints, health, and in-flight coordination entries. */
|
|
50646
|
+
reset() {
|
|
50647
|
+
this.entries.clear();
|
|
50648
|
+
this.inFlight.clear();
|
|
50649
|
+
}
|
|
50650
|
+
entry(id) {
|
|
50651
|
+
const existing = this.entries.get(id);
|
|
50652
|
+
if (existing) return existing;
|
|
50653
|
+
const created = {};
|
|
50654
|
+
this.entries.set(id, created);
|
|
50655
|
+
return created;
|
|
50656
|
+
}
|
|
50657
|
+
};
|
|
50658
|
+
|
|
50659
|
+
// src/sandbox_plugin/host/pluginHostProtocol.ts
|
|
50660
|
+
var INLINE_INPUT_LIMIT_BYTES2 = 48 * 1024;
|
|
50661
|
+
var DEFAULT_HANDLER_TIMEOUT_MS = 6e4;
|
|
50662
|
+
var MIN_HANDLER_TIMEOUT_MS = 1e3;
|
|
50663
|
+
var MAX_HANDLER_TIMEOUT_MS = 3e5;
|
|
50664
|
+
function isRecord11(value) {
|
|
50665
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50666
|
+
}
|
|
50667
|
+
function isEnvelope3(value) {
|
|
50668
|
+
return isRecord11(value) && typeof value.ok === "boolean";
|
|
50669
|
+
}
|
|
50670
|
+
function parseHostArgs(argv) {
|
|
50671
|
+
let type;
|
|
50672
|
+
let port;
|
|
50673
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
50674
|
+
const token = argv[index];
|
|
50675
|
+
if (token === "--type" && index + 1 < argv.length) {
|
|
50676
|
+
type = argv[index + 1];
|
|
50677
|
+
index += 1;
|
|
50678
|
+
} else if (token.startsWith("--type=")) {
|
|
50679
|
+
type = token.slice("--type=".length);
|
|
50680
|
+
} else if (token === "--port" && index + 1 < argv.length) {
|
|
50681
|
+
port = argv[index + 1];
|
|
50682
|
+
index += 1;
|
|
50683
|
+
} else if (token.startsWith("--port=")) {
|
|
50684
|
+
port = token.slice("--port=".length);
|
|
50685
|
+
}
|
|
50686
|
+
}
|
|
50687
|
+
const parsedPort = Number(port);
|
|
50688
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
50689
|
+
throw new Error("parseHostArgs: missing or empty --type");
|
|
50690
|
+
}
|
|
50691
|
+
if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
|
|
50692
|
+
throw new Error("parseHostArgs: invalid --port");
|
|
50693
|
+
}
|
|
50694
|
+
return { type, port: parsedPort };
|
|
50695
|
+
}
|
|
50696
|
+
function resolveHandlerTimeoutMs(value) {
|
|
50697
|
+
if (value === null || value === void 0 || value === "") return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
50698
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
50699
|
+
if (!Number.isFinite(numeric)) return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
50700
|
+
return Math.min(Math.max(Math.round(numeric), MIN_HANDLER_TIMEOUT_MS), MAX_HANDLER_TIMEOUT_MS);
|
|
50701
|
+
}
|
|
50702
|
+
function parseHandlerEnvelope(stdout) {
|
|
50703
|
+
const trimmed = stdout.trim();
|
|
50704
|
+
if (!trimmed) return { ok: false, error: "empty handler output" };
|
|
50705
|
+
const whole = tryParse(trimmed);
|
|
50706
|
+
if (isEnvelope3(whole)) return normalizeEnvelope(whole);
|
|
50707
|
+
const lines = stdout.split("\n");
|
|
50708
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
50709
|
+
const line = lines[index]?.trim();
|
|
50710
|
+
if (!line) continue;
|
|
50711
|
+
const candidate = tryParse(line);
|
|
50712
|
+
if (isEnvelope3(candidate)) return normalizeEnvelope(candidate);
|
|
50713
|
+
}
|
|
50714
|
+
return { ok: true, result: trimmed };
|
|
50715
|
+
}
|
|
50716
|
+
function buildHandlerCommand(handlerPath) {
|
|
50717
|
+
return `node ${quotePosixShellArg2(handlerPath)}`;
|
|
50718
|
+
}
|
|
50719
|
+
function encodeInput(input) {
|
|
50720
|
+
return Buffer.from(JSON.stringify(input ?? null), "utf8").toString("base64");
|
|
50721
|
+
}
|
|
50722
|
+
function buildConnectionPayload2(args) {
|
|
50723
|
+
if (Array.isArray(args.connections) && args.connections.length > 0) {
|
|
50724
|
+
return {
|
|
50725
|
+
connection: args.connection ?? args.connections[0]?.config,
|
|
50726
|
+
connections: args.connections
|
|
50727
|
+
};
|
|
50728
|
+
}
|
|
50729
|
+
if (args.connection) {
|
|
50730
|
+
return { connection: args.connection };
|
|
50731
|
+
}
|
|
50732
|
+
return null;
|
|
50733
|
+
}
|
|
50734
|
+
function quotePosixShellArg2(value) {
|
|
50735
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
50736
|
+
}
|
|
50737
|
+
function tryParse(text) {
|
|
50738
|
+
try {
|
|
50739
|
+
return JSON.parse(text);
|
|
50740
|
+
} catch {
|
|
50741
|
+
return void 0;
|
|
50742
|
+
}
|
|
50743
|
+
}
|
|
50744
|
+
function normalizeEnvelope(parsed) {
|
|
50745
|
+
if (parsed.ok) return { ok: true, result: parsed.result };
|
|
50746
|
+
return { ok: false, error: typeof parsed.error === "string" ? parsed.error : void 0 };
|
|
50747
|
+
}
|
|
50748
|
+
|
|
50749
|
+
// src/sandbox_plugin/host/pluginHostSource.ts
|
|
50750
|
+
var import_node_crypto15 = require("crypto");
|
|
50751
|
+
var PLUGIN_HOST_VERSION = "1.1.0";
|
|
50752
|
+
var PLUGIN_HOST_SERVER_SOURCE = String.raw`#!/usr/bin/env node
|
|
50753
|
+
"use strict";
|
|
50754
|
+
|
|
50755
|
+
var http = require("node:http");
|
|
50756
|
+
var fs = require("node:fs");
|
|
50757
|
+
var os = require("node:os");
|
|
50758
|
+
var path = require("node:path");
|
|
50759
|
+
var crypto = require("node:crypto");
|
|
50760
|
+
var execFile = require("node:child_process").execFile;
|
|
50761
|
+
|
|
50762
|
+
var VERSION = "${PLUGIN_HOST_VERSION}";
|
|
50763
|
+
var PLUGINS_ROOT = "/root/.agents/plugins";
|
|
50764
|
+
var INLINE_INPUT_LIMIT_BYTES = 48 * 1024;
|
|
50765
|
+
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
50766
|
+
var MAX_RESULT_BYTES = 16 * 1024 * 1024;
|
|
50767
|
+
var DEFAULT_HANDLER_TIMEOUT_MS = 60000;
|
|
50768
|
+
var MIN_HANDLER_TIMEOUT_MS = 1000;
|
|
50769
|
+
var MAX_HANDLER_TIMEOUT_MS = 300000;
|
|
50770
|
+
|
|
50771
|
+
function resolveHandlerTimeoutMs(value) {
|
|
50772
|
+
if (value === null || value === undefined || value === "") {
|
|
50773
|
+
return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
50774
|
+
}
|
|
50775
|
+
var numeric = typeof value === "number" ? value : Number(value);
|
|
50776
|
+
if (!isFinite(numeric)) {
|
|
50777
|
+
return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
50778
|
+
}
|
|
50779
|
+
return Math.min(Math.max(Math.round(numeric), MIN_HANDLER_TIMEOUT_MS), MAX_HANDLER_TIMEOUT_MS);
|
|
50780
|
+
}
|
|
50781
|
+
|
|
50782
|
+
function isRecord(value) {
|
|
50783
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
50784
|
+
}
|
|
50785
|
+
|
|
50786
|
+
function isEnvelope(value) {
|
|
50787
|
+
return isRecord(value) && typeof value.ok === "boolean";
|
|
50788
|
+
}
|
|
50789
|
+
|
|
50790
|
+
function tryParse(text) {
|
|
50791
|
+
try {
|
|
50792
|
+
return JSON.parse(text);
|
|
50793
|
+
} catch (err) {
|
|
50794
|
+
return undefined;
|
|
50795
|
+
}
|
|
50796
|
+
}
|
|
50797
|
+
|
|
50798
|
+
function normalizeEnvelope(parsed) {
|
|
50799
|
+
if (parsed.ok) {
|
|
50800
|
+
return { ok: true, result: parsed.result };
|
|
50801
|
+
}
|
|
50802
|
+
return { ok: false, error: typeof parsed.error === "string" ? parsed.error : undefined };
|
|
50803
|
+
}
|
|
50804
|
+
|
|
50805
|
+
function parseHandlerEnvelope(stdout) {
|
|
50806
|
+
var text = String(stdout == null ? "" : stdout);
|
|
50807
|
+
var trimmed = text.trim();
|
|
50808
|
+
if (!trimmed) {
|
|
50809
|
+
// Deliberate divergence from SandboxPluginExecutor, which returns
|
|
50810
|
+
// { ok: true, result: "" } for empty stdout; the resident host treats empty
|
|
50811
|
+
// output as a handler failure so a crashed/no-op handler is not a "success".
|
|
50812
|
+
return { ok: false, error: "empty handler output" };
|
|
50813
|
+
}
|
|
50814
|
+
var whole = tryParse(trimmed);
|
|
50815
|
+
if (isEnvelope(whole)) {
|
|
50816
|
+
return normalizeEnvelope(whole);
|
|
50817
|
+
}
|
|
50818
|
+
var lines = text.split("\n");
|
|
50819
|
+
for (var index = lines.length - 1; index >= 0; index -= 1) {
|
|
50820
|
+
var line = lines[index].trim();
|
|
50821
|
+
if (!line) {
|
|
50822
|
+
continue;
|
|
50823
|
+
}
|
|
50824
|
+
var candidate = tryParse(line);
|
|
50825
|
+
if (isEnvelope(candidate)) {
|
|
50826
|
+
return normalizeEnvelope(candidate);
|
|
50827
|
+
}
|
|
50828
|
+
}
|
|
50829
|
+
return { ok: true, result: trimmed };
|
|
50830
|
+
}
|
|
50831
|
+
|
|
50832
|
+
function quotePosixShellArg(value) {
|
|
50833
|
+
return "'" + String(value).replace(/'/g, "'\"'\"'") + "'";
|
|
50834
|
+
}
|
|
50835
|
+
|
|
50836
|
+
function buildHandlerCommand(handlerPath) {
|
|
50837
|
+
return "node " + quotePosixShellArg(handlerPath);
|
|
50838
|
+
}
|
|
50839
|
+
|
|
50840
|
+
function parseHostArgs(argv) {
|
|
50841
|
+
var type;
|
|
50842
|
+
var port;
|
|
50843
|
+
for (var index = 0; index < argv.length; index += 1) {
|
|
50844
|
+
var token = argv[index];
|
|
50845
|
+
if (token === "--type" && index + 1 < argv.length) {
|
|
50846
|
+
type = argv[index + 1];
|
|
50847
|
+
index += 1;
|
|
50848
|
+
} else if (token.indexOf("--type=") === 0) {
|
|
50849
|
+
type = token.slice("--type=".length);
|
|
50850
|
+
} else if (token === "--port" && index + 1 < argv.length) {
|
|
50851
|
+
port = argv[index + 1];
|
|
50852
|
+
index += 1;
|
|
50853
|
+
} else if (token.indexOf("--port=") === 0) {
|
|
50854
|
+
port = token.slice("--port=".length);
|
|
50855
|
+
}
|
|
50856
|
+
}
|
|
50857
|
+
var parsedPort = Number(port);
|
|
50858
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
50859
|
+
throw new Error("parseHostArgs: missing or empty --type");
|
|
50860
|
+
}
|
|
50861
|
+
if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
|
|
50862
|
+
throw new Error("parseHostArgs: invalid --port");
|
|
50863
|
+
}
|
|
50864
|
+
return { type: type, port: parsedPort };
|
|
50865
|
+
}
|
|
50866
|
+
|
|
50867
|
+
function encodeInput(input) {
|
|
50868
|
+
return Buffer.from(JSON.stringify(input === undefined ? null : input), "utf8").toString("base64");
|
|
50869
|
+
}
|
|
50870
|
+
|
|
50871
|
+
function buildConnectionPayload(body) {
|
|
50872
|
+
if (Array.isArray(body.connections) && body.connections.length > 0) {
|
|
50873
|
+
return {
|
|
50874
|
+
connection: body.connection !== undefined ? body.connection : body.connections[0].config,
|
|
50875
|
+
connections: body.connections,
|
|
50876
|
+
};
|
|
50877
|
+
}
|
|
50878
|
+
if (body.connection) {
|
|
50879
|
+
return { connection: body.connection };
|
|
50880
|
+
}
|
|
50881
|
+
return null;
|
|
50882
|
+
}
|
|
50883
|
+
|
|
50884
|
+
function resolveHandlerPath(type, handler) {
|
|
50885
|
+
if (typeof handler !== "string" || handler.length === 0) {
|
|
50886
|
+
throw new Error("missing handler");
|
|
50887
|
+
}
|
|
50888
|
+
if (handler.charAt(0) === "/" || handler.split("/").indexOf("..") !== -1) {
|
|
50889
|
+
throw new Error("invalid handler path");
|
|
50890
|
+
}
|
|
50891
|
+
return path.join(PLUGINS_ROOT, type, handler);
|
|
50892
|
+
}
|
|
50893
|
+
|
|
50894
|
+
function readJsonBody(req) {
|
|
50895
|
+
return new Promise(function (resolve, reject) {
|
|
50896
|
+
var chunks = [];
|
|
50897
|
+
var size = 0;
|
|
50898
|
+
req.on("data", function (chunk) {
|
|
50899
|
+
size += chunk.length;
|
|
50900
|
+
if (size > MAX_BODY_BYTES) {
|
|
50901
|
+
reject(new Error("request body too large"));
|
|
50902
|
+
req.destroy();
|
|
50903
|
+
return;
|
|
50904
|
+
}
|
|
50905
|
+
chunks.push(chunk);
|
|
50906
|
+
});
|
|
50907
|
+
req.on("end", function () {
|
|
50908
|
+
try {
|
|
50909
|
+
var text = Buffer.concat(chunks).toString("utf8");
|
|
50910
|
+
resolve(text ? JSON.parse(text) : {});
|
|
50911
|
+
} catch (err) {
|
|
50912
|
+
reject(new Error("invalid JSON body"));
|
|
50913
|
+
}
|
|
50914
|
+
});
|
|
50915
|
+
req.on("error", reject);
|
|
50916
|
+
});
|
|
50917
|
+
}
|
|
50918
|
+
|
|
50919
|
+
function runNode(handlerPath, pluginDir, env, timeoutMs) {
|
|
50920
|
+
return new Promise(function (resolve) {
|
|
50921
|
+
execFile(
|
|
50922
|
+
process.execPath,
|
|
50923
|
+
[handlerPath],
|
|
50924
|
+
{ cwd: pluginDir, env: env, timeout: timeoutMs, maxBuffer: MAX_RESULT_BYTES },
|
|
50925
|
+
function (error, stdout, stderr) {
|
|
50926
|
+
resolve({ error: error, stdout: stdout || "", stderr: stderr || "" });
|
|
50927
|
+
}
|
|
50928
|
+
);
|
|
50929
|
+
});
|
|
50930
|
+
}
|
|
50931
|
+
|
|
50932
|
+
function failureFromExec(exec, command) {
|
|
50933
|
+
var parsed = parseHandlerEnvelope(exec.stdout);
|
|
50934
|
+
if (!parsed.ok) {
|
|
50935
|
+
return parsed;
|
|
50936
|
+
}
|
|
50937
|
+
var code = exec.error && typeof exec.error.code === "number" ? exec.error.code : "n/a";
|
|
50938
|
+
var detail = exec.stdout ? exec.stdout.slice(0, 4096) : exec.error ? String(exec.error.message) : "";
|
|
50939
|
+
return { ok: false, error: "handler failed (exit " + code + "): " + detail + " [" + command + "]" };
|
|
50940
|
+
}
|
|
50941
|
+
|
|
50942
|
+
async function runHandler(body) {
|
|
50943
|
+
var pluginDir = path.join(PLUGINS_ROOT, config.type);
|
|
50944
|
+
var handlerPath = resolveHandlerPath(config.type, body.handler);
|
|
50945
|
+
var command = buildHandlerCommand(handlerPath);
|
|
50946
|
+
var inputJson = JSON.stringify(body.input === undefined ? null : body.input);
|
|
50947
|
+
var connectionPayload = buildConnectionPayload(body);
|
|
50948
|
+
var inputIsLarge = Buffer.byteLength(inputJson, "utf8") > INLINE_INPUT_LIMIT_BYTES;
|
|
50949
|
+
var needsTempDir = inputIsLarge || connectionPayload !== null;
|
|
50950
|
+
var tempDir = null;
|
|
50951
|
+
var env = Object.assign({}, process.env);
|
|
50952
|
+
try {
|
|
50953
|
+
if (needsTempDir) {
|
|
50954
|
+
tempDir = path.join(os.tmpdir(), "plugin-run-" + Date.now() + "-" + Math.random().toString(36).slice(2));
|
|
50955
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
50956
|
+
}
|
|
50957
|
+
if (inputIsLarge) {
|
|
50958
|
+
var inputFile = path.join(tempDir, "input.json");
|
|
50959
|
+
fs.writeFileSync(inputFile, inputJson);
|
|
50960
|
+
env.PLUGIN_INPUT_FILE = inputFile;
|
|
50961
|
+
} else {
|
|
50962
|
+
env.PLUGIN_INPUT = encodeInput(body.input);
|
|
50963
|
+
}
|
|
50964
|
+
if (connectionPayload !== null) {
|
|
50965
|
+
var connectionFile = path.join(tempDir, "connection.json");
|
|
50966
|
+
fs.writeFileSync(connectionFile, JSON.stringify(connectionPayload));
|
|
50967
|
+
env.PLUGIN_CONNECTION_FILE = connectionFile;
|
|
50968
|
+
}
|
|
50969
|
+
env.PLUGIN_CONTEXT = encodeInput(body.context || {});
|
|
50970
|
+
var handlerTimeoutMs = resolveHandlerTimeoutMs(body.timeoutMs);
|
|
50971
|
+
var exec = await runNode(handlerPath, pluginDir, env, handlerTimeoutMs);
|
|
50972
|
+
if (exec.error) {
|
|
50973
|
+
return failureFromExec(exec, command);
|
|
50974
|
+
}
|
|
50975
|
+
return parseHandlerEnvelope(exec.stdout);
|
|
50976
|
+
} finally {
|
|
50977
|
+
if (tempDir) {
|
|
50978
|
+
try {
|
|
50979
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
50980
|
+
} catch (err) {
|
|
50981
|
+
// best-effort cleanup
|
|
50982
|
+
}
|
|
50983
|
+
}
|
|
50984
|
+
}
|
|
50985
|
+
}
|
|
50986
|
+
|
|
50987
|
+
function manifestHash() {
|
|
50988
|
+
try {
|
|
50989
|
+
var content = fs.readFileSync(path.join(PLUGINS_ROOT, config.type, "plugin.json"), "utf8");
|
|
50990
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
50991
|
+
} catch (err) {
|
|
50992
|
+
return "";
|
|
50993
|
+
}
|
|
50994
|
+
}
|
|
50995
|
+
|
|
50996
|
+
function sendJson(res, status, payload) {
|
|
50997
|
+
var body = JSON.stringify(payload);
|
|
50998
|
+
res.writeHead(status, {
|
|
50999
|
+
"content-type": "application/json",
|
|
51000
|
+
"content-length": Buffer.byteLength(body),
|
|
51001
|
+
});
|
|
51002
|
+
res.end(body);
|
|
51003
|
+
}
|
|
51004
|
+
|
|
51005
|
+
function handleRequest(req, res) {
|
|
51006
|
+
var url = String(req.url || "").split("?")[0];
|
|
51007
|
+
if (req.method === "GET" && url === "/health") {
|
|
51008
|
+
sendJson(res, 200, { ok: true, version: VERSION });
|
|
51009
|
+
return;
|
|
51010
|
+
}
|
|
51011
|
+
if (req.method === "GET" && url === "/meta") {
|
|
51012
|
+
sendJson(res, 200, { type: config.type, manifestHash: manifestHash() });
|
|
51013
|
+
return;
|
|
51014
|
+
}
|
|
51015
|
+
if (req.method === "POST" && url === "/invoke") {
|
|
51016
|
+
readJsonBody(req)
|
|
51017
|
+
.then(function (body) {
|
|
51018
|
+
if (!isRecord(body)) {
|
|
51019
|
+
sendJson(res, 400, { ok: false, error: "invalid body" });
|
|
51020
|
+
return;
|
|
51021
|
+
}
|
|
51022
|
+
return runHandler(body).then(function (result) {
|
|
51023
|
+
sendJson(res, 200, result);
|
|
51024
|
+
});
|
|
51025
|
+
})
|
|
51026
|
+
.catch(function (err) {
|
|
51027
|
+
sendJson(res, 400, { ok: false, error: String(err && err.message ? err.message : err) });
|
|
51028
|
+
});
|
|
51029
|
+
return;
|
|
51030
|
+
}
|
|
51031
|
+
sendJson(res, 404, { ok: false, error: "not found" });
|
|
51032
|
+
}
|
|
51033
|
+
|
|
51034
|
+
var config = parseHostArgs(process.argv);
|
|
51035
|
+
var server = http.createServer(handleRequest);
|
|
51036
|
+
server.listen(config.port, "0.0.0.0", function () {
|
|
51037
|
+
process.stdout.write(
|
|
51038
|
+
"[plugin-host] listening type=" + config.type + " port=" + config.port + " version=" + VERSION + "\n"
|
|
51039
|
+
);
|
|
51040
|
+
});
|
|
51041
|
+
`;
|
|
51042
|
+
function pluginHostContentHash() {
|
|
51043
|
+
return (0, import_node_crypto15.createHash)("sha256").update(`${PLUGIN_HOST_VERSION}:${PLUGIN_HOST_SERVER_SOURCE}`).digest("hex").slice(0, 16);
|
|
51044
|
+
}
|
|
51045
|
+
|
|
51046
|
+
// src/sandbox_plugin/host/pluginHostInstaller.ts
|
|
51047
|
+
var PLUGIN_HOST_DIR = "/root/.agents/.plugin-host";
|
|
51048
|
+
var SERVER_PATH = `${PLUGIN_HOST_DIR}/server.js`;
|
|
51049
|
+
var VERSION_PATH = `${PLUGIN_HOST_DIR}/version.json`;
|
|
51050
|
+
async function installPluginHost(sandbox) {
|
|
51051
|
+
const hash = pluginHostContentHash();
|
|
51052
|
+
try {
|
|
51053
|
+
const existing = await sandbox.file.readFile(VERSION_PATH);
|
|
51054
|
+
const parsed = JSON.parse(existing.content);
|
|
51055
|
+
if (parsed.hash === hash) {
|
|
51056
|
+
return { path: SERVER_PATH, version: PLUGIN_HOST_VERSION, hash, changed: false };
|
|
51057
|
+
}
|
|
51058
|
+
} catch {
|
|
51059
|
+
}
|
|
51060
|
+
await sandbox.file.createDirectory(PLUGIN_HOST_DIR).catch(() => void 0);
|
|
51061
|
+
await sandbox.file.writeFile(SERVER_PATH, PLUGIN_HOST_SERVER_SOURCE);
|
|
51062
|
+
await sandbox.file.writeFile(VERSION_PATH, JSON.stringify({ version: PLUGIN_HOST_VERSION, hash }, null, 2));
|
|
51063
|
+
return { path: SERVER_PATH, version: PLUGIN_HOST_VERSION, hash, changed: true };
|
|
51064
|
+
}
|
|
51065
|
+
|
|
49298
51066
|
// src/workflow/index.ts
|
|
49299
51067
|
init_compile();
|
|
49300
51068
|
init_parse_yaml();
|
|
@@ -49686,16 +51454,16 @@ var IdRemapper = class {
|
|
|
49686
51454
|
};
|
|
49687
51455
|
|
|
49688
51456
|
// src/util/directRoomId.ts
|
|
49689
|
-
var
|
|
51457
|
+
var import_node_crypto16 = require("crypto");
|
|
49690
51458
|
function generateDirectHumanProjectId(tenantId2, workspaceId, userId1, userId2) {
|
|
49691
51459
|
const sortedUserIds = [userId1, userId2].sort();
|
|
49692
51460
|
const parts = [tenantId2, workspaceId, ...sortedUserIds];
|
|
49693
|
-
const hash = (0,
|
|
51461
|
+
const hash = (0, import_node_crypto16.createHash)("sha256").update(parts.join(":")).digest("hex");
|
|
49694
51462
|
return `direct_human_${hash}`;
|
|
49695
51463
|
}
|
|
49696
51464
|
function generateDirectBotProjectId(tenantId2, workspaceId, userId, assistantId) {
|
|
49697
51465
|
const parts = [tenantId2, workspaceId, userId, assistantId];
|
|
49698
|
-
const hash = (0,
|
|
51466
|
+
const hash = (0, import_node_crypto16.createHash)("sha256").update(parts.join(":")).digest("hex");
|
|
49699
51467
|
return `direct_bot_${hash}`;
|
|
49700
51468
|
}
|
|
49701
51469
|
function parseDirectRoomType(projectId) {
|
|
@@ -49725,6 +51493,7 @@ registerBuiltinPlugins();
|
|
|
49725
51493
|
CapabilityBundleValidationError,
|
|
49726
51494
|
CapabilityToolNameConflictError,
|
|
49727
51495
|
CapabilityToolRegistry,
|
|
51496
|
+
ChannelConnectionLeader,
|
|
49728
51497
|
ChunkBuffer,
|
|
49729
51498
|
ChunkBufferLatticeManager,
|
|
49730
51499
|
CollectionLatticeManager,
|
|
@@ -49736,7 +51505,9 @@ registerBuiltinPlugins();
|
|
|
49736
51505
|
DEFAULT_CALIBRATION_PROBES,
|
|
49737
51506
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
49738
51507
|
DEFAULT_DELAY_MS,
|
|
51508
|
+
DEFAULT_HANDLER_TIMEOUT_MS,
|
|
49739
51509
|
DEFAULT_MAX_ATTEMPTS,
|
|
51510
|
+
DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS,
|
|
49740
51511
|
DaytonaInstance,
|
|
49741
51512
|
DaytonaProvider,
|
|
49742
51513
|
DefaultScheduleClient,
|
|
@@ -49754,6 +51525,7 @@ registerBuiltinPlugins();
|
|
|
49754
51525
|
HEALTH_CHECK_INTERVAL_MS,
|
|
49755
51526
|
HEALTH_CHECK_TIMEOUT_MS,
|
|
49756
51527
|
HumanMessage,
|
|
51528
|
+
INLINE_INPUT_LIMIT_BYTES,
|
|
49757
51529
|
IdRemapper,
|
|
49758
51530
|
InMemoryA2AApiKeyStore,
|
|
49759
51531
|
InMemoryAgentWebAppStore,
|
|
@@ -49794,10 +51566,12 @@ registerBuiltinPlugins();
|
|
|
49794
51566
|
LocalSandboxProvider,
|
|
49795
51567
|
LoggerLatticeManager,
|
|
49796
51568
|
MAX_BACKOFF_MS,
|
|
51569
|
+
MAX_HANDLER_TIMEOUT_MS,
|
|
49797
51570
|
MAX_LINE_LENGTH,
|
|
49798
51571
|
MAX_PACK_ENTRIES,
|
|
49799
51572
|
MAX_PACK_FILE_BYTES,
|
|
49800
51573
|
MAX_PACK_TOTAL_BYTES,
|
|
51574
|
+
MIN_HANDLER_TIMEOUT_MS,
|
|
49801
51575
|
ManagedTaskStartError,
|
|
49802
51576
|
McpLatticeManager,
|
|
49803
51577
|
MemoryBackend,
|
|
@@ -49816,8 +51590,12 @@ registerBuiltinPlugins();
|
|
|
49816
51590
|
PACK_DIRECTORY_TIMEOUT_SECONDS,
|
|
49817
51591
|
PLATFORM_SECRET_ENV_VARS,
|
|
49818
51592
|
PLUGIN_CATALOG_CHANGED_TOPIC,
|
|
51593
|
+
PLUGIN_HOST_DIR,
|
|
51594
|
+
PLUGIN_HOST_SERVER_SOURCE,
|
|
51595
|
+
PLUGIN_HOST_VERSION,
|
|
49819
51596
|
PersonalAssistantConfig,
|
|
49820
51597
|
PinoLoggerClient,
|
|
51598
|
+
PluginDeploymentRuntimeRegistry,
|
|
49821
51599
|
PluginRegistry,
|
|
49822
51600
|
PostgresDatabase,
|
|
49823
51601
|
ProjectBotMembershipIdConflictError,
|
|
@@ -49879,7 +51657,9 @@ registerBuiltinPlugins();
|
|
|
49879
51657
|
appendUiFence,
|
|
49880
51658
|
assertSafeUpstreamUrl,
|
|
49881
51659
|
buildBundleFromDist,
|
|
51660
|
+
buildConnectionPayload,
|
|
49882
51661
|
buildGrepResultsDict,
|
|
51662
|
+
buildHandlerCommand,
|
|
49883
51663
|
buildInput,
|
|
49884
51664
|
buildNamedVolumeName,
|
|
49885
51665
|
buildRemotePackDirectoryCommand,
|
|
@@ -49947,9 +51727,11 @@ registerBuiltinPlugins();
|
|
|
49947
51727
|
decrypt,
|
|
49948
51728
|
decryptConfigFields,
|
|
49949
51729
|
describeCronExpression,
|
|
51730
|
+
disconnectAllChannels,
|
|
49950
51731
|
documentLearningPlugin,
|
|
49951
51732
|
documentParserPlugin,
|
|
49952
51733
|
embeddingsLatticeManager,
|
|
51734
|
+
encodeInput,
|
|
49953
51735
|
encrypt,
|
|
49954
51736
|
encryptConfigFields,
|
|
49955
51737
|
ensureBuiltinAgentsForTenant,
|
|
@@ -49993,6 +51775,7 @@ registerBuiltinPlugins();
|
|
|
49993
51775
|
getModelLattice,
|
|
49994
51776
|
getNextCronTime,
|
|
49995
51777
|
getOrCreateCollectionVectorStore,
|
|
51778
|
+
getPluginDeploymentService,
|
|
49996
51779
|
getPluginSkillManifestVersion,
|
|
49997
51780
|
getQueueLattice,
|
|
49998
51781
|
getReplicaId,
|
|
@@ -50018,9 +51801,12 @@ registerBuiltinPlugins();
|
|
|
50018
51801
|
globSearchFiles,
|
|
50019
51802
|
grepMatchesFromFiles,
|
|
50020
51803
|
grepSearchFiles,
|
|
51804
|
+
handlerResultText,
|
|
50021
51805
|
hasChunkBuffer,
|
|
50022
51806
|
hasDelegatedWorkerToken,
|
|
50023
51807
|
hasRuntimeCapabilityPolicy,
|
|
51808
|
+
installPluginHost,
|
|
51809
|
+
invokeResident,
|
|
50024
51810
|
invokeWithRetry,
|
|
50025
51811
|
isBuiltInSkill,
|
|
50026
51812
|
isDirectRoom,
|
|
@@ -50048,6 +51834,8 @@ registerBuiltinPlugins();
|
|
|
50048
51834
|
parallelLimit,
|
|
50049
51835
|
parseCronExpression,
|
|
50050
51836
|
parseDirectRoomType,
|
|
51837
|
+
parseHandlerEnvelope,
|
|
51838
|
+
parseHostArgs,
|
|
50051
51839
|
parseJudgeVerdict,
|
|
50052
51840
|
parsePackSize,
|
|
50053
51841
|
parseQueuedExecutionMode,
|
|
@@ -50056,6 +51844,7 @@ registerBuiltinPlugins();
|
|
|
50056
51844
|
parseTrustedRunContext,
|
|
50057
51845
|
parseYaml,
|
|
50058
51846
|
performStringReplacement,
|
|
51847
|
+
pluginHostContentHash,
|
|
50059
51848
|
postProcessTaskMutationResponse,
|
|
50060
51849
|
prepareTaskDispatch,
|
|
50061
51850
|
projectMutationMutex,
|
|
@@ -50094,6 +51883,7 @@ registerBuiltinPlugins();
|
|
|
50094
51883
|
renderTemplate,
|
|
50095
51884
|
resetReplicaIdForTests,
|
|
50096
51885
|
resolveEnabledPluginSkill,
|
|
51886
|
+
resolveHandlerTimeoutMs,
|
|
50097
51887
|
resolveJudgeModelKey,
|
|
50098
51888
|
resolvePath,
|
|
50099
51889
|
resolvePluginConnections,
|
|
@@ -50113,6 +51903,7 @@ registerBuiltinPlugins();
|
|
|
50113
51903
|
setBindingRegistry,
|
|
50114
51904
|
setEvalRunService,
|
|
50115
51905
|
setMenuRegistry,
|
|
51906
|
+
setPluginDeploymentService,
|
|
50116
51907
|
setSandboxPluginCatalog,
|
|
50117
51908
|
skillLatticeManager,
|
|
50118
51909
|
sqlDatabaseManager,
|