@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.mjs
CHANGED
|
@@ -1688,6 +1688,9 @@ var InMemoryProjectStore = class {
|
|
|
1688
1688
|
workspaceId,
|
|
1689
1689
|
name: data.name,
|
|
1690
1690
|
description: data.description,
|
|
1691
|
+
// Persist `kind` like the Local/PostgreSQL stores so `public`/`personal`/
|
|
1692
|
+
// `training` project semantics survive in in-memory deployments.
|
|
1693
|
+
...data.kind === void 0 ? {} : { kind: data.kind },
|
|
1691
1694
|
config: data.config === void 0 && Object.keys(capabilityBundleIds).length === 0 ? void 0 : { ...data.config ?? {}, ...capabilityBundleIds },
|
|
1692
1695
|
createdAt: existing?.createdAt ?? now,
|
|
1693
1696
|
updatedAt: now
|
|
@@ -3307,6 +3310,93 @@ var InMemoryOutboxStore = class {
|
|
|
3307
3310
|
}
|
|
3308
3311
|
};
|
|
3309
3312
|
|
|
3313
|
+
// src/store_lattice/InMemoryPluginDeploymentStore.ts
|
|
3314
|
+
var InMemoryPluginDeploymentStore = class {
|
|
3315
|
+
constructor() {
|
|
3316
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
3317
|
+
}
|
|
3318
|
+
/**
|
|
3319
|
+
* Lists every deployment for a tenant.
|
|
3320
|
+
*
|
|
3321
|
+
* @param tenantId - Tenant whose deployments are listed
|
|
3322
|
+
* @returns Defensive copies of the tenant's deployments
|
|
3323
|
+
*/
|
|
3324
|
+
async listByTenant(tenantId2) {
|
|
3325
|
+
return [...this.entries.values()].filter((entry) => entry.tenantId === tenantId2).map((entry) => structuredClone(entry));
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Returns the deployment for a tenant + plugin type.
|
|
3329
|
+
*
|
|
3330
|
+
* @param tenantId - Owning tenant
|
|
3331
|
+
* @param pluginType - Plugin type key
|
|
3332
|
+
* @returns Defensive copy of the deployment, or undefined when absent
|
|
3333
|
+
*/
|
|
3334
|
+
async get(tenantId2, pluginType) {
|
|
3335
|
+
const entry = this.entries.get(entryKey2(tenantId2, pluginType));
|
|
3336
|
+
return entry ? structuredClone(entry) : void 0;
|
|
3337
|
+
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Inserts or replaces a deployment keyed by tenant + plugin type.
|
|
3340
|
+
*
|
|
3341
|
+
* On conflict the existing `id` and `createdAt` are preserved while every
|
|
3342
|
+
* other field (including `updatedAt`, when supplied) is updated — mirroring
|
|
3343
|
+
* the PostgreSQL store's `ON CONFLICT` semantics.
|
|
3344
|
+
*
|
|
3345
|
+
* @param deployment - Deployment record to persist
|
|
3346
|
+
* @returns Defensive copy of the stored deployment
|
|
3347
|
+
*/
|
|
3348
|
+
async upsert(deployment) {
|
|
3349
|
+
const key5 = entryKey2(deployment.tenantId, deployment.pluginType);
|
|
3350
|
+
const existing = this.entries.get(key5);
|
|
3351
|
+
const next = {
|
|
3352
|
+
...structuredClone(deployment),
|
|
3353
|
+
id: existing?.id ?? deployment.id,
|
|
3354
|
+
createdAt: existing?.createdAt ?? deployment.createdAt,
|
|
3355
|
+
updatedAt: deployment.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3356
|
+
};
|
|
3357
|
+
this.entries.set(key5, next);
|
|
3358
|
+
return structuredClone(next);
|
|
3359
|
+
}
|
|
3360
|
+
/**
|
|
3361
|
+
* Applies a partial update to a deployment.
|
|
3362
|
+
*
|
|
3363
|
+
* `patch.createdAt` is ignored (immutable) and `updatedAt` is taken from the
|
|
3364
|
+
* patch when supplied, otherwise set to the current time — matching the
|
|
3365
|
+
* PostgreSQL store.
|
|
3366
|
+
*
|
|
3367
|
+
* @param tenantId - Owning tenant
|
|
3368
|
+
* @param pluginType - Plugin type key
|
|
3369
|
+
* @param patch - Mutable fields to apply
|
|
3370
|
+
* @returns Defensive copy of the updated deployment, or undefined when absent
|
|
3371
|
+
*/
|
|
3372
|
+
async updateStatus(tenantId2, pluginType, patch) {
|
|
3373
|
+
const key5 = entryKey2(tenantId2, pluginType);
|
|
3374
|
+
const existing = this.entries.get(key5);
|
|
3375
|
+
if (!existing) return void 0;
|
|
3376
|
+
const next = {
|
|
3377
|
+
...existing,
|
|
3378
|
+
...patch,
|
|
3379
|
+
createdAt: existing.createdAt,
|
|
3380
|
+
updatedAt: patch.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
3381
|
+
};
|
|
3382
|
+
this.entries.set(key5, next);
|
|
3383
|
+
return structuredClone(next);
|
|
3384
|
+
}
|
|
3385
|
+
/**
|
|
3386
|
+
* Deletes a deployment by tenant + plugin type.
|
|
3387
|
+
*
|
|
3388
|
+
* @param tenantId - Owning tenant
|
|
3389
|
+
* @param pluginType - Plugin type key
|
|
3390
|
+
* @returns Whether a record was removed
|
|
3391
|
+
*/
|
|
3392
|
+
async delete(tenantId2, pluginType) {
|
|
3393
|
+
return this.entries.delete(entryKey2(tenantId2, pluginType));
|
|
3394
|
+
}
|
|
3395
|
+
};
|
|
3396
|
+
function entryKey2(tenantId2, pluginType) {
|
|
3397
|
+
return JSON.stringify([tenantId2, pluginType]);
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3310
3400
|
// src/store_lattice/InMemoryUserStore.ts
|
|
3311
3401
|
var InMemoryUserStore = class {
|
|
3312
3402
|
constructor() {
|
|
@@ -4977,6 +5067,7 @@ var defaultProjectRoomReadStateStore = new InMemoryProjectRoomReadStateStore();
|
|
|
4977
5067
|
var defaultUserPushSubscriptionStore = new InMemoryUserPushSubscriptionStore();
|
|
4978
5068
|
var defaultLeaseStore = new InMemoryLeaseStore();
|
|
4979
5069
|
var defaultOutboxStore = new InMemoryOutboxStore();
|
|
5070
|
+
var defaultPluginDeploymentStore = new InMemoryPluginDeploymentStore();
|
|
4980
5071
|
storeLatticeManager.registerLattice("default", "thread", defaultThreadStore);
|
|
4981
5072
|
storeLatticeManager.registerLattice(
|
|
4982
5073
|
"default",
|
|
@@ -5000,6 +5091,7 @@ storeLatticeManager.registerLattice("default", "projectRoomReadState", defaultPr
|
|
|
5000
5091
|
storeLatticeManager.registerLattice("default", "userPushSubscription", defaultUserPushSubscriptionStore);
|
|
5001
5092
|
storeLatticeManager.registerLattice("default", "lease", defaultLeaseStore);
|
|
5002
5093
|
storeLatticeManager.registerLattice("default", "outbox", defaultOutboxStore);
|
|
5094
|
+
storeLatticeManager.registerLattice("default", "pluginDeployment", defaultPluginDeploymentStore);
|
|
5003
5095
|
var defaultUserStore = new InMemoryUserStore();
|
|
5004
5096
|
var defaultTenantStore = new InMemoryTenantStore();
|
|
5005
5097
|
var defaultUserTenantLinkStore = new InMemoryUserTenantLinkStore();
|
|
@@ -13062,6 +13154,82 @@ var evalPlugin = {
|
|
|
13062
13154
|
})
|
|
13063
13155
|
};
|
|
13064
13156
|
|
|
13157
|
+
// src/sandbox_plugin/residentTransport.ts
|
|
13158
|
+
var DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS = 6e4;
|
|
13159
|
+
function isRecord3(value) {
|
|
13160
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13161
|
+
}
|
|
13162
|
+
function isEnvelope(value) {
|
|
13163
|
+
return isRecord3(value) && typeof value.ok === "boolean";
|
|
13164
|
+
}
|
|
13165
|
+
function errorFromBody(value) {
|
|
13166
|
+
if (isRecord3(value) && typeof value.error === "string" && value.error.length > 0) {
|
|
13167
|
+
return value.error;
|
|
13168
|
+
}
|
|
13169
|
+
return void 0;
|
|
13170
|
+
}
|
|
13171
|
+
async function readJson(response) {
|
|
13172
|
+
try {
|
|
13173
|
+
return await response.json();
|
|
13174
|
+
} catch {
|
|
13175
|
+
return void 0;
|
|
13176
|
+
}
|
|
13177
|
+
}
|
|
13178
|
+
async function invokeResident(args) {
|
|
13179
|
+
const {
|
|
13180
|
+
baseUrl,
|
|
13181
|
+
endpointPath,
|
|
13182
|
+
serviceKey,
|
|
13183
|
+
payload,
|
|
13184
|
+
timeoutMs = DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS
|
|
13185
|
+
} = args;
|
|
13186
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
13187
|
+
const url = `${baseUrl.replace(/\/$/, "")}${endpointPath}/invoke`;
|
|
13188
|
+
const headers = {
|
|
13189
|
+
"Content-Type": "application/json"
|
|
13190
|
+
};
|
|
13191
|
+
if (serviceKey) {
|
|
13192
|
+
headers["Authorization"] = `Bearer ${serviceKey}`;
|
|
13193
|
+
}
|
|
13194
|
+
const controller = new AbortController();
|
|
13195
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
13196
|
+
try {
|
|
13197
|
+
const response = await fetchImpl(url, {
|
|
13198
|
+
method: "POST",
|
|
13199
|
+
headers,
|
|
13200
|
+
body: JSON.stringify({ ...payload, timeoutMs: payload.timeoutMs ?? timeoutMs }),
|
|
13201
|
+
signal: controller.signal
|
|
13202
|
+
});
|
|
13203
|
+
const parsed = await readJson(response);
|
|
13204
|
+
if (!response.ok) {
|
|
13205
|
+
return {
|
|
13206
|
+
ok: false,
|
|
13207
|
+
error: errorFromBody(parsed) ?? `resident service responded with HTTP ${response.status}`,
|
|
13208
|
+
status: response.status
|
|
13209
|
+
};
|
|
13210
|
+
}
|
|
13211
|
+
if (!isEnvelope(parsed)) {
|
|
13212
|
+
return {
|
|
13213
|
+
ok: false,
|
|
13214
|
+
error: "resident service returned an invalid envelope",
|
|
13215
|
+
status: response.status
|
|
13216
|
+
};
|
|
13217
|
+
}
|
|
13218
|
+
if (!parsed.ok) {
|
|
13219
|
+
return {
|
|
13220
|
+
ok: false,
|
|
13221
|
+
error: errorFromBody(parsed) ?? "resident handler reported failure",
|
|
13222
|
+
status: response.status
|
|
13223
|
+
};
|
|
13224
|
+
}
|
|
13225
|
+
return { ok: true, result: parsed.result, status: response.status };
|
|
13226
|
+
} catch (error) {
|
|
13227
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
13228
|
+
} finally {
|
|
13229
|
+
clearTimeout(timer);
|
|
13230
|
+
}
|
|
13231
|
+
}
|
|
13232
|
+
|
|
13065
13233
|
// src/sandbox_plugin/SandboxPluginExecutor.ts
|
|
13066
13234
|
var PLUGINS_ROOT = "/root/.agents/plugins";
|
|
13067
13235
|
var INLINE_INPUT_LIMIT_BYTES = 48 * 1024;
|
|
@@ -13074,11 +13242,11 @@ var MAX_MAX_RESULT_BYTES = 1048576;
|
|
|
13074
13242
|
function encode(value) {
|
|
13075
13243
|
return Buffer.from(JSON.stringify(value ?? null), "utf8").toString("base64");
|
|
13076
13244
|
}
|
|
13077
|
-
function
|
|
13245
|
+
function isRecord4(value) {
|
|
13078
13246
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13079
13247
|
}
|
|
13080
|
-
function
|
|
13081
|
-
return
|
|
13248
|
+
function isEnvelope2(value) {
|
|
13249
|
+
return isRecord4(value) && typeof value.ok === "boolean";
|
|
13082
13250
|
}
|
|
13083
13251
|
function clamp(value, min, max) {
|
|
13084
13252
|
return Math.min(Math.max(value, min), max);
|
|
@@ -13099,7 +13267,7 @@ function parseEnvelope(stdout) {
|
|
|
13099
13267
|
const trimmed = stdout.trim();
|
|
13100
13268
|
try {
|
|
13101
13269
|
const parsed = JSON.parse(trimmed || "null");
|
|
13102
|
-
if (
|
|
13270
|
+
if (isEnvelope2(parsed)) return parsed;
|
|
13103
13271
|
} catch {
|
|
13104
13272
|
}
|
|
13105
13273
|
const lines = stdout.split("\n");
|
|
@@ -13108,7 +13276,7 @@ function parseEnvelope(stdout) {
|
|
|
13108
13276
|
if (!line) continue;
|
|
13109
13277
|
try {
|
|
13110
13278
|
const parsed = JSON.parse(line);
|
|
13111
|
-
if (
|
|
13279
|
+
if (isEnvelope2(parsed)) return parsed;
|
|
13112
13280
|
} catch {
|
|
13113
13281
|
}
|
|
13114
13282
|
}
|
|
@@ -13121,26 +13289,264 @@ function truncate(value, cap) {
|
|
|
13121
13289
|
while (end > 0 && (buf[end] & 192) === 128) end -= 1;
|
|
13122
13290
|
return buf.subarray(0, end).toString("utf8") + "\n...[truncated]";
|
|
13123
13291
|
}
|
|
13292
|
+
function capResult(result, cap) {
|
|
13293
|
+
const serialized = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
13294
|
+
return Buffer.byteLength(serialized, "utf8") <= cap ? result : truncate(serialized, cap);
|
|
13295
|
+
}
|
|
13296
|
+
function handlerResultText(result) {
|
|
13297
|
+
const value = result.result;
|
|
13298
|
+
if (typeof value === "string") return value;
|
|
13299
|
+
if (value === void 0) return (result.stdout ?? "").trim();
|
|
13300
|
+
return JSON.stringify(value);
|
|
13301
|
+
}
|
|
13124
13302
|
function errorFromStdout(stdout) {
|
|
13125
13303
|
let text = stdout;
|
|
13126
13304
|
try {
|
|
13127
13305
|
const parsed = JSON.parse(stdout);
|
|
13128
|
-
if (
|
|
13306
|
+
if (isRecord4(parsed)) {
|
|
13129
13307
|
text = JSON.stringify(sanitize(parsed)) ?? stdout;
|
|
13130
13308
|
}
|
|
13131
13309
|
} catch {
|
|
13132
13310
|
}
|
|
13133
13311
|
return text.slice(0, 4096);
|
|
13134
13312
|
}
|
|
13313
|
+
function buildResidentPayload(args, timeoutMs) {
|
|
13314
|
+
return {
|
|
13315
|
+
handler: args.handler,
|
|
13316
|
+
input: args.input,
|
|
13317
|
+
...args.connection === void 0 ? {} : { connection: args.connection },
|
|
13318
|
+
...args.connections === void 0 ? {} : { connections: args.connections },
|
|
13319
|
+
context: args.context,
|
|
13320
|
+
timeoutMs
|
|
13321
|
+
};
|
|
13322
|
+
}
|
|
13135
13323
|
var SandboxPluginExecutor = class {
|
|
13136
13324
|
/**
|
|
13137
|
-
* @param deps - Sandbox resolver and optional
|
|
13325
|
+
* @param deps - Sandbox resolver, optional logger, and optional resident routing
|
|
13138
13326
|
*/
|
|
13139
13327
|
constructor(deps) {
|
|
13140
13328
|
this.deps = deps;
|
|
13141
13329
|
}
|
|
13142
13330
|
/**
|
|
13143
|
-
* Invoke a plugin handler
|
|
13331
|
+
* Invoke a plugin handler.
|
|
13332
|
+
*
|
|
13333
|
+
* Routes to the published plugin's resident service when {@link
|
|
13334
|
+
* SandboxPluginExecutorDeps.resident} is configured and a deployment is
|
|
13335
|
+
* routable (`running` with an endpoint, or a resumed `onDemand` deployment).
|
|
13336
|
+
* Otherwise, or when the resident call fails and degradation is enabled, runs
|
|
13337
|
+
* the handler as a short `node <handler>` process inside the tenant sandbox.
|
|
13338
|
+
*
|
|
13339
|
+
* @param args - Invocation arguments including manifest, handler, and input
|
|
13340
|
+
* @returns The normalized handler result, exit code, timing, and raw stdout
|
|
13341
|
+
* @throws When `args.handler` is not a safe relative path
|
|
13342
|
+
*/
|
|
13343
|
+
async invoke(args) {
|
|
13344
|
+
const started = Date.now();
|
|
13345
|
+
validateRelativePath(args.handler, "handler");
|
|
13346
|
+
if (this.deps.resident) {
|
|
13347
|
+
const resident = await this.tryResident(args, this.deps.resident, started);
|
|
13348
|
+
if (resident) return resident;
|
|
13349
|
+
}
|
|
13350
|
+
return this.invokeShortProcess(args, started);
|
|
13351
|
+
}
|
|
13352
|
+
/**
|
|
13353
|
+
* Attempt to route an invocation to a published plugin's resident service.
|
|
13354
|
+
*
|
|
13355
|
+
* @param args - Invocation arguments
|
|
13356
|
+
* @param resident - Resident routing dependencies
|
|
13357
|
+
* @param started - Invocation start timestamp used for `durationMs`
|
|
13358
|
+
* @returns A terminal result (success, or failure with degradation disabled),
|
|
13359
|
+
* or `undefined` when the caller should fall back to the short-process path
|
|
13360
|
+
*/
|
|
13361
|
+
async tryResident(args, resident, started) {
|
|
13362
|
+
const tenantId2 = args.runConfig?.tenantId ?? args.context.tenantId;
|
|
13363
|
+
const pluginType = args.manifest.type;
|
|
13364
|
+
if (!tenantId2) return void 0;
|
|
13365
|
+
const deployment = await this.lookupDeployment(resident, tenantId2, pluginType);
|
|
13366
|
+
if (!deployment) return void 0;
|
|
13367
|
+
const timeoutMs = clamp(args.timeoutMs ?? DEFAULT_TIMEOUT_MS, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS);
|
|
13368
|
+
const payload = buildResidentPayload(args, timeoutMs);
|
|
13369
|
+
if (deployment.status === "running" && deployment.endpoint) {
|
|
13370
|
+
let outcome = await this.callResident(resident, deployment.endpoint, payload, timeoutMs);
|
|
13371
|
+
if (!outcome.ok && deployment.mode === "onDemand" && resident.ensureRunning) {
|
|
13372
|
+
outcome = await this.ensureRunningAndRetry(
|
|
13373
|
+
resident,
|
|
13374
|
+
tenantId2,
|
|
13375
|
+
pluginType,
|
|
13376
|
+
deployment.endpoint,
|
|
13377
|
+
payload,
|
|
13378
|
+
timeoutMs,
|
|
13379
|
+
outcome
|
|
13380
|
+
);
|
|
13381
|
+
}
|
|
13382
|
+
return this.finalizeResident(outcome, args, resident, pluginType, tenantId2, started);
|
|
13383
|
+
}
|
|
13384
|
+
if (deployment.mode === "onDemand" && resident.ensureRunning) {
|
|
13385
|
+
const outcome = await this.wakeOnDemand(resident, tenantId2, pluginType, payload, timeoutMs);
|
|
13386
|
+
if (outcome) {
|
|
13387
|
+
return this.finalizeResident(outcome, args, resident, pluginType, tenantId2, started);
|
|
13388
|
+
}
|
|
13389
|
+
}
|
|
13390
|
+
return void 0;
|
|
13391
|
+
}
|
|
13392
|
+
/**
|
|
13393
|
+
* Normalize a resident invocation outcome into the caller-facing result.
|
|
13394
|
+
*
|
|
13395
|
+
* @param outcome - Normalized resident transport result
|
|
13396
|
+
* @param args - Invocation arguments (for the result-size cap)
|
|
13397
|
+
* @param resident - Resident routing dependencies (degradation policy)
|
|
13398
|
+
* @param pluginType - Plugin type, for diagnostics
|
|
13399
|
+
* @param tenantId - Tenant owning the plugin, for diagnostics
|
|
13400
|
+
* @param started - Invocation start timestamp used for `durationMs`
|
|
13401
|
+
* @returns The normalized result, or `undefined` when the caller should fall
|
|
13402
|
+
* back to the short-process path
|
|
13403
|
+
*/
|
|
13404
|
+
finalizeResident(outcome, args, resident, pluginType, tenantId2, started) {
|
|
13405
|
+
if (outcome.ok) {
|
|
13406
|
+
const cap = clamp(
|
|
13407
|
+
args.maxResultBytes ?? DEFAULT_MAX_RESULT_BYTES,
|
|
13408
|
+
MIN_MAX_RESULT_BYTES,
|
|
13409
|
+
MAX_MAX_RESULT_BYTES
|
|
13410
|
+
);
|
|
13411
|
+
return {
|
|
13412
|
+
ok: true,
|
|
13413
|
+
result: capResult(outcome.result, cap),
|
|
13414
|
+
exitCode: 0,
|
|
13415
|
+
httpStatus: outcome.status,
|
|
13416
|
+
durationMs: Date.now() - started,
|
|
13417
|
+
stdout: "",
|
|
13418
|
+
stderr: ""
|
|
13419
|
+
};
|
|
13420
|
+
}
|
|
13421
|
+
const error = outcome.error ?? "resident plugin invocation failed";
|
|
13422
|
+
if (resident.degradeToShortProcess ?? true) {
|
|
13423
|
+
this.deps.logger?.info(
|
|
13424
|
+
{ event: "sandbox-plugin.resident.degrade", pluginType, tenantId: tenantId2, error },
|
|
13425
|
+
"resident plugin invocation failed; falling back to short process"
|
|
13426
|
+
);
|
|
13427
|
+
return void 0;
|
|
13428
|
+
}
|
|
13429
|
+
return {
|
|
13430
|
+
ok: false,
|
|
13431
|
+
error,
|
|
13432
|
+
exitCode: 1,
|
|
13433
|
+
httpStatus: outcome.status,
|
|
13434
|
+
durationMs: Date.now() - started,
|
|
13435
|
+
stdout: "",
|
|
13436
|
+
stderr: ""
|
|
13437
|
+
};
|
|
13438
|
+
}
|
|
13439
|
+
/**
|
|
13440
|
+
* Look up a deployment, degrading to `undefined` when the lookup throws.
|
|
13441
|
+
*
|
|
13442
|
+
* @param resident - Resident routing dependencies
|
|
13443
|
+
* @param tenantId - Tenant owning the plugin
|
|
13444
|
+
* @param pluginType - Plugin type addressed by the invocation
|
|
13445
|
+
* @returns The deployment, or `undefined` when absent or lookup failed
|
|
13446
|
+
*/
|
|
13447
|
+
async lookupDeployment(resident, tenantId2, pluginType) {
|
|
13448
|
+
try {
|
|
13449
|
+
return await resident.getDeployment(tenantId2, pluginType);
|
|
13450
|
+
} catch (error) {
|
|
13451
|
+
this.deps.logger?.info(
|
|
13452
|
+
{
|
|
13453
|
+
event: "sandbox-plugin.resident.lookup.failed",
|
|
13454
|
+
pluginType,
|
|
13455
|
+
tenantId: tenantId2,
|
|
13456
|
+
error: error instanceof Error ? error.message : String(error)
|
|
13457
|
+
},
|
|
13458
|
+
"resident deployment lookup failed; falling back to short process"
|
|
13459
|
+
);
|
|
13460
|
+
return void 0;
|
|
13461
|
+
}
|
|
13462
|
+
}
|
|
13463
|
+
/**
|
|
13464
|
+
* Resume a non-running `onDemand` deployment once, then invoke it.
|
|
13465
|
+
*
|
|
13466
|
+
* Re-reads the deployment after the resume so a cold-started endpoint is
|
|
13467
|
+
* picked up. Never throws: a failed resume or a deployment that is still not
|
|
13468
|
+
* `running` with an endpoint resolves to `undefined`, letting the caller use
|
|
13469
|
+
* the short-process path.
|
|
13470
|
+
*
|
|
13471
|
+
* @param resident - Resident routing dependencies
|
|
13472
|
+
* @param tenantId - Tenant owning the plugin
|
|
13473
|
+
* @param pluginType - Plugin type addressed by the invocation
|
|
13474
|
+
* @param payload - Invocation payload
|
|
13475
|
+
* @param timeoutMs - Clamped per-call timeout
|
|
13476
|
+
* @returns The resident outcome when routed, or `undefined` to short-process
|
|
13477
|
+
*/
|
|
13478
|
+
async wakeOnDemand(resident, tenantId2, pluginType, payload, timeoutMs) {
|
|
13479
|
+
try {
|
|
13480
|
+
await resident.ensureRunning?.(tenantId2, pluginType);
|
|
13481
|
+
} catch (error) {
|
|
13482
|
+
this.deps.logger?.info(
|
|
13483
|
+
{
|
|
13484
|
+
event: "sandbox-plugin.resident.ensure.failed",
|
|
13485
|
+
pluginType,
|
|
13486
|
+
tenantId: tenantId2,
|
|
13487
|
+
error: error instanceof Error ? error.message : String(error)
|
|
13488
|
+
},
|
|
13489
|
+
"resident service resume failed; falling back to short process"
|
|
13490
|
+
);
|
|
13491
|
+
return void 0;
|
|
13492
|
+
}
|
|
13493
|
+
const refreshed = await this.lookupDeployment(resident, tenantId2, pluginType);
|
|
13494
|
+
if (!refreshed || refreshed.status !== "running" || !refreshed.endpoint) {
|
|
13495
|
+
return void 0;
|
|
13496
|
+
}
|
|
13497
|
+
return this.callResident(resident, refreshed.endpoint, payload, timeoutMs);
|
|
13498
|
+
}
|
|
13499
|
+
/**
|
|
13500
|
+
* Resume an `onDemand` deployment once and retry the resident call once.
|
|
13501
|
+
*
|
|
13502
|
+
* @param resident - Resident routing dependencies
|
|
13503
|
+
* @param tenantId - Tenant owning the plugin
|
|
13504
|
+
* @param pluginType - Plugin type addressed by the invocation
|
|
13505
|
+
* @param endpoint - Endpoint used by the first attempt
|
|
13506
|
+
* @param payload - Invocation payload
|
|
13507
|
+
* @param timeoutMs - Clamped per-call timeout
|
|
13508
|
+
* @param firstOutcome - Failed first attempt, returned when resume fails
|
|
13509
|
+
* @returns The retry outcome, or `firstOutcome` when resume itself failed
|
|
13510
|
+
*/
|
|
13511
|
+
async ensureRunningAndRetry(resident, tenantId2, pluginType, endpoint, payload, timeoutMs, firstOutcome) {
|
|
13512
|
+
try {
|
|
13513
|
+
await resident.ensureRunning?.(tenantId2, pluginType);
|
|
13514
|
+
} catch (error) {
|
|
13515
|
+
this.deps.logger?.info(
|
|
13516
|
+
{
|
|
13517
|
+
event: "sandbox-plugin.resident.ensure.failed",
|
|
13518
|
+
pluginType,
|
|
13519
|
+
tenantId: tenantId2,
|
|
13520
|
+
error: error instanceof Error ? error.message : String(error)
|
|
13521
|
+
},
|
|
13522
|
+
"resident service resume failed"
|
|
13523
|
+
);
|
|
13524
|
+
return firstOutcome;
|
|
13525
|
+
}
|
|
13526
|
+
const refreshed = await this.lookupDeployment(resident, tenantId2, pluginType);
|
|
13527
|
+
return this.callResident(resident, refreshed?.endpoint ?? endpoint, payload, timeoutMs);
|
|
13528
|
+
}
|
|
13529
|
+
/**
|
|
13530
|
+
* Invoke the resident host over the sandbox-service proxy path.
|
|
13531
|
+
*
|
|
13532
|
+
* @param resident - Resident routing dependencies
|
|
13533
|
+
* @param endpointPath - Relative proxy path
|
|
13534
|
+
* @param payload - Invocation payload matching the host contract
|
|
13535
|
+
* @param timeoutMs - Clamped per-call timeout
|
|
13536
|
+
* @returns The normalized resident invocation result
|
|
13537
|
+
*/
|
|
13538
|
+
callResident(resident, endpointPath, payload, timeoutMs) {
|
|
13539
|
+
return invokeResident({
|
|
13540
|
+
baseUrl: resident.baseUrl,
|
|
13541
|
+
endpointPath,
|
|
13542
|
+
serviceKey: resident.serviceKey,
|
|
13543
|
+
payload,
|
|
13544
|
+
timeoutMs,
|
|
13545
|
+
fetchImpl: resident.fetchImpl
|
|
13546
|
+
});
|
|
13547
|
+
}
|
|
13548
|
+
/**
|
|
13549
|
+
* Invoke a plugin handler as a short `node <handler>` process.
|
|
13144
13550
|
*
|
|
13145
13551
|
* Resolves the sandbox, injects small input as a base64 env var (or writes a
|
|
13146
13552
|
* temp `input.json` for large input), writes connection credentials to a temp
|
|
@@ -13150,12 +13556,10 @@ var SandboxPluginExecutor = class {
|
|
|
13150
13556
|
* in all cases, including setup failures.
|
|
13151
13557
|
*
|
|
13152
13558
|
* @param args - Invocation arguments including manifest, handler, and input
|
|
13559
|
+
* @param started - Invocation start timestamp used for `durationMs`
|
|
13153
13560
|
* @returns The normalized handler result, exit code, timing, and raw stdout
|
|
13154
|
-
* @throws When `args.handler` is not a safe relative path
|
|
13155
13561
|
*/
|
|
13156
|
-
async
|
|
13157
|
-
const started = Date.now();
|
|
13158
|
-
validateRelativePath(args.handler, "handler");
|
|
13562
|
+
async invokeShortProcess(args, started) {
|
|
13159
13563
|
const pluginDir = `${PLUGINS_ROOT}/${args.manifest.type}`;
|
|
13160
13564
|
const handlerAbs = `${pluginDir}/${args.handler}`;
|
|
13161
13565
|
const tenantId2 = args.runConfig?.tenantId ?? args.context.tenantId;
|
|
@@ -13243,11 +13647,9 @@ var SandboxPluginExecutor = class {
|
|
|
13243
13647
|
}
|
|
13244
13648
|
const parsed = parseEnvelope(stdout);
|
|
13245
13649
|
if (parsed) {
|
|
13246
|
-
const result = parsed.ok ? parsed.result : void 0;
|
|
13247
|
-
const serialized = typeof result === "string" ? result : JSON.stringify(result ?? null);
|
|
13248
13650
|
return {
|
|
13249
13651
|
ok: parsed.ok,
|
|
13250
|
-
result:
|
|
13652
|
+
result: capResult(parsed.ok ? parsed.result : void 0, cap),
|
|
13251
13653
|
error: typeof parsed.error === "string" ? parsed.error : void 0,
|
|
13252
13654
|
exitCode,
|
|
13253
13655
|
durationMs,
|
|
@@ -13258,7 +13660,7 @@ var SandboxPluginExecutor = class {
|
|
|
13258
13660
|
const text = stdout.trim();
|
|
13259
13661
|
return {
|
|
13260
13662
|
ok: true,
|
|
13261
|
-
result:
|
|
13663
|
+
result: capResult(text, cap),
|
|
13262
13664
|
exitCode,
|
|
13263
13665
|
durationMs,
|
|
13264
13666
|
stdout,
|
|
@@ -13275,24 +13677,33 @@ var SandboxPluginExecutor = class {
|
|
|
13275
13677
|
}
|
|
13276
13678
|
};
|
|
13277
13679
|
|
|
13680
|
+
// src/sandbox_plugin/pluginDeploymentServiceRuntime.ts
|
|
13681
|
+
var service2 = null;
|
|
13682
|
+
function setPluginDeploymentService(instance) {
|
|
13683
|
+
service2 = instance ?? null;
|
|
13684
|
+
}
|
|
13685
|
+
function getPluginDeploymentService() {
|
|
13686
|
+
return service2;
|
|
13687
|
+
}
|
|
13688
|
+
|
|
13278
13689
|
// src/sandbox_plugin/TenantPluginCatalog.ts
|
|
13279
13690
|
import { createHash as createHash3 } from "crypto";
|
|
13280
13691
|
|
|
13281
13692
|
// src/sandbox_plugin/jsonSchemaToZod.ts
|
|
13282
13693
|
import { z as z43 } from "zod";
|
|
13283
|
-
function
|
|
13694
|
+
function isRecord5(value) {
|
|
13284
13695
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13285
13696
|
}
|
|
13286
13697
|
function withDescription(schema6, raw) {
|
|
13287
|
-
const description =
|
|
13698
|
+
const description = isRecord5(raw) ? raw.description : void 0;
|
|
13288
13699
|
return typeof description === "string" ? schema6.describe(description) : schema6;
|
|
13289
13700
|
}
|
|
13290
13701
|
function jsonSchemaToZod(schema6) {
|
|
13291
|
-
if (!
|
|
13702
|
+
if (!isRecord5(schema6)) throw new Error("schema must be an object");
|
|
13292
13703
|
const type = schema6.type;
|
|
13293
13704
|
switch (type) {
|
|
13294
13705
|
case "object": {
|
|
13295
|
-
const props =
|
|
13706
|
+
const props = isRecord5(schema6.properties) ? schema6.properties : {};
|
|
13296
13707
|
const required = Array.isArray(schema6.required) ? schema6.required.filter((x) => typeof x === "string") : [];
|
|
13297
13708
|
const shape = {};
|
|
13298
13709
|
for (const [key5, sub] of Object.entries(props)) {
|
|
@@ -13332,7 +13743,7 @@ var DISPLAY_MODES = ["inline", "fullscreen", "pip"];
|
|
|
13332
13743
|
var MAX_MANIFEST_BYTES = 262144;
|
|
13333
13744
|
var MAX_TOOLS = 32;
|
|
13334
13745
|
var MAX_PLUGINS_PER_TENANT = 50;
|
|
13335
|
-
function
|
|
13746
|
+
function isRecord6(value) {
|
|
13336
13747
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13337
13748
|
}
|
|
13338
13749
|
function isSafeHandler(path8) {
|
|
@@ -13357,7 +13768,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
13357
13768
|
const diagnostics = [];
|
|
13358
13769
|
const error = (code, message, pluginType, tool68) => diagnostics.push({ level: "error", code, message, pluginType, tool: tool68 });
|
|
13359
13770
|
const raw = args.raw;
|
|
13360
|
-
if (!
|
|
13771
|
+
if (!isRecord6(raw)) {
|
|
13361
13772
|
error("MANIFEST_NOT_OBJECT", "manifest must be a JSON object", args.dirName);
|
|
13362
13773
|
return { tools: [], diagnostics };
|
|
13363
13774
|
}
|
|
@@ -13410,7 +13821,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
13410
13821
|
const tools = [];
|
|
13411
13822
|
const seenToolNames = /* @__PURE__ */ new Set();
|
|
13412
13823
|
for (const candidate of toolCandidates.slice(0, MAX_TOOLS)) {
|
|
13413
|
-
if (!
|
|
13824
|
+
if (!isRecord6(candidate)) {
|
|
13414
13825
|
error("TOOL_INVALID", "tool must be an object", type);
|
|
13415
13826
|
continue;
|
|
13416
13827
|
}
|
|
@@ -13427,7 +13838,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
13427
13838
|
error("HANDLER_MISSING", `handler "${String(candidate.handler)}" not found`, type, name);
|
|
13428
13839
|
continue;
|
|
13429
13840
|
}
|
|
13430
|
-
if (!
|
|
13841
|
+
if (!isRecord6(candidate.schema)) {
|
|
13431
13842
|
error("TOOL_SCHEMA", `tool "${name}" schema must be an object`, type, name);
|
|
13432
13843
|
continue;
|
|
13433
13844
|
}
|
|
@@ -13441,17 +13852,17 @@ function validateSandboxPluginManifest(args) {
|
|
|
13441
13852
|
let ui;
|
|
13442
13853
|
if (candidate.ui !== void 0) {
|
|
13443
13854
|
const rawUi = candidate.ui;
|
|
13444
|
-
if (!
|
|
13855
|
+
if (!isRecord6(rawUi) || !isSafeUiPath(rawUi.resource) || !args.handlerExists(rawUi.resource)) {
|
|
13445
13856
|
error("TOOL_UI", `tool "${name}" ui.resource must be an existing .html file within the plugin dir`, type, name);
|
|
13446
13857
|
} else {
|
|
13447
|
-
const csp =
|
|
13858
|
+
const csp = isRecord6(rawUi.csp) ? {
|
|
13448
13859
|
connectDomains: Array.isArray(rawUi.csp.connectDomains) ? rawUi.csp.connectDomains.filter((d) => typeof d === "string") : void 0,
|
|
13449
13860
|
resourceDomains: Array.isArray(rawUi.csp.resourceDomains) ? rawUi.csp.resourceDomains.filter((d) => typeof d === "string") : void 0,
|
|
13450
13861
|
frameDomains: Array.isArray(rawUi.csp.frameDomains) ? rawUi.csp.frameDomains.filter((d) => typeof d === "string") : void 0,
|
|
13451
13862
|
baseUriDomains: Array.isArray(rawUi.csp.baseUriDomains) ? rawUi.csp.baseUriDomains.filter((d) => typeof d === "string") : void 0
|
|
13452
13863
|
} : void 0;
|
|
13453
13864
|
const displayMode = DISPLAY_MODES.includes(rawUi.displayMode) ? rawUi.displayMode : void 0;
|
|
13454
|
-
const permissions =
|
|
13865
|
+
const permissions = isRecord6(rawUi.permissions) ? {
|
|
13455
13866
|
camera: rawUi.permissions.camera === true ? true : void 0,
|
|
13456
13867
|
microphone: rawUi.permissions.microphone === true ? true : void 0,
|
|
13457
13868
|
geolocation: rawUi.permissions.geolocation === true ? true : void 0,
|
|
@@ -13481,7 +13892,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
13481
13892
|
}
|
|
13482
13893
|
const skills = {};
|
|
13483
13894
|
if (raw.skills !== void 0) {
|
|
13484
|
-
if (!
|
|
13895
|
+
if (!isRecord6(raw.skills)) {
|
|
13485
13896
|
error("SKILLS_INVALID", "skills must be an object", type);
|
|
13486
13897
|
} else {
|
|
13487
13898
|
const prefix = `${type}-`;
|
|
@@ -13496,12 +13907,12 @@ function validateSandboxPluginManifest(args) {
|
|
|
13496
13907
|
error("SKILL_NAME", `skill "${name}" must be a safe relative path prefixed with "${prefix}"`, type);
|
|
13497
13908
|
continue;
|
|
13498
13909
|
}
|
|
13499
|
-
if (!
|
|
13910
|
+
if (!isRecord6(definition) || typeof definition.version !== "string" || typeof definition.content !== "string") {
|
|
13500
13911
|
error("SKILL_INVALID", `skill "${name}" requires string version and content`, type);
|
|
13501
13912
|
continue;
|
|
13502
13913
|
}
|
|
13503
13914
|
if (definition.resources !== void 0) {
|
|
13504
|
-
if (!
|
|
13915
|
+
if (!isRecord6(definition.resources)) {
|
|
13505
13916
|
error("SKILL_RESOURCES", `skill "${name}" resources must be an object`, type);
|
|
13506
13917
|
continue;
|
|
13507
13918
|
}
|
|
@@ -13513,7 +13924,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
13513
13924
|
resourcesOk = false;
|
|
13514
13925
|
break;
|
|
13515
13926
|
}
|
|
13516
|
-
if (!
|
|
13927
|
+
if (!isRecord6(resource) || typeof resource.content !== "string") {
|
|
13517
13928
|
resourcesOk = false;
|
|
13518
13929
|
break;
|
|
13519
13930
|
}
|
|
@@ -13529,11 +13940,11 @@ function validateSandboxPluginManifest(args) {
|
|
|
13529
13940
|
}
|
|
13530
13941
|
const agents = {};
|
|
13531
13942
|
if (raw.agents !== void 0) {
|
|
13532
|
-
if (!
|
|
13943
|
+
if (!isRecord6(raw.agents)) {
|
|
13533
13944
|
error("AGENTS_INVALID", "agents must be an object", type);
|
|
13534
13945
|
} else {
|
|
13535
13946
|
for (const [key5, config] of Object.entries(raw.agents)) {
|
|
13536
|
-
if (!
|
|
13947
|
+
if (!isRecord6(config)) {
|
|
13537
13948
|
error("AGENT_INVALID", `agent "${key5}" must be an object`, type);
|
|
13538
13949
|
continue;
|
|
13539
13950
|
}
|
|
@@ -13554,7 +13965,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
13554
13965
|
}
|
|
13555
13966
|
}
|
|
13556
13967
|
let connection;
|
|
13557
|
-
if (
|
|
13968
|
+
if (isRecord6(raw.connection)) {
|
|
13558
13969
|
const fields = raw.connection.fields;
|
|
13559
13970
|
if (!Array.isArray(fields)) {
|
|
13560
13971
|
error("CONNECTION_INVALID", "connection.fields must be an array", type);
|
|
@@ -13568,7 +13979,7 @@ function validateSandboxPluginManifest(args) {
|
|
|
13568
13979
|
for (const key5 of ["test", "discover"]) {
|
|
13569
13980
|
const entry = raw.connection[key5];
|
|
13570
13981
|
if (entry === void 0) continue;
|
|
13571
|
-
const handler =
|
|
13982
|
+
const handler = isRecord6(entry) ? entry.handler : void 0;
|
|
13572
13983
|
if (!isSafeHandler(handler) || !args.handlerExists(handler)) {
|
|
13573
13984
|
error("CONNECTION_HANDLER", `connection.${key5} handler "${String(handler)}" not found`, type);
|
|
13574
13985
|
continue;
|
|
@@ -13586,8 +13997,8 @@ function validateSandboxPluginManifest(args) {
|
|
|
13586
13997
|
version: raw.version,
|
|
13587
13998
|
icon: typeof raw.icon === "string" ? raw.icon : void 0,
|
|
13588
13999
|
category: typeof raw.category === "string" ? raw.category : void 0,
|
|
13589
|
-
configSchema:
|
|
13590
|
-
defaultConfig:
|
|
14000
|
+
configSchema: isRecord6(raw.configSchema) ? raw.configSchema : void 0,
|
|
14001
|
+
defaultConfig: isRecord6(raw.defaultConfig) ? raw.defaultConfig : void 0,
|
|
13591
14002
|
connection,
|
|
13592
14003
|
tools,
|
|
13593
14004
|
skills: Object.keys(skills).length > 0 ? skills : void 0,
|
|
@@ -13767,7 +14178,7 @@ var STANDARD_CONNECTION_DEFAULT_CONFIG = Object.freeze({
|
|
|
13767
14178
|
connectAll: false
|
|
13768
14179
|
});
|
|
13769
14180
|
var EMPTY_DEFAULT_CONFIG = Object.freeze({});
|
|
13770
|
-
function
|
|
14181
|
+
function isRecord7(value) {
|
|
13771
14182
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13772
14183
|
}
|
|
13773
14184
|
function runConfigFrom(exeConfig) {
|
|
@@ -13819,7 +14230,7 @@ function compileSandboxPlugin(args) {
|
|
|
13819
14230
|
maxResultBytes: t.maxResultBytes
|
|
13820
14231
|
});
|
|
13821
14232
|
if (!result.ok) return result.error ?? "sandbox plugin execution failed";
|
|
13822
|
-
const text =
|
|
14233
|
+
const text = handlerResultText(result);
|
|
13823
14234
|
const ref = t.ui ? { kind: "plugin", pluginType: manifest.type, resource: t.ui.resource, displayMode: t.ui.displayMode ?? "inline" } : null;
|
|
13824
14235
|
return ref ? appendUiFence(text, ref) : text;
|
|
13825
14236
|
},
|
|
@@ -13857,11 +14268,11 @@ async function runConnectionHandler(executor, manifest, handler, config, tenantI
|
|
|
13857
14268
|
return { ok: false, message: result.error ?? "connection test failed" };
|
|
13858
14269
|
}
|
|
13859
14270
|
const value = result.result;
|
|
13860
|
-
if (
|
|
14271
|
+
if (isRecord7(value) && typeof value.ok === "boolean" && typeof value.message === "string") {
|
|
13861
14272
|
return {
|
|
13862
14273
|
ok: value.ok,
|
|
13863
14274
|
message: value.message,
|
|
13864
|
-
details:
|
|
14275
|
+
details: isRecord7(value.details) ? value.details : void 0
|
|
13865
14276
|
};
|
|
13866
14277
|
}
|
|
13867
14278
|
return { ok: false, message: result.error ?? "connection test returned an invalid result" };
|
|
@@ -14214,13 +14625,29 @@ var TenantPluginCatalog = class {
|
|
|
14214
14625
|
|
|
14215
14626
|
// src/sandbox_plugin/sandboxPluginRuntime.ts
|
|
14216
14627
|
var catalog = null;
|
|
14628
|
+
function buildResidentRouting() {
|
|
14629
|
+
const baseUrl = process.env.MICROSANDBOX_SERVICE_BASE_URL;
|
|
14630
|
+
if (!baseUrl) return void 0;
|
|
14631
|
+
return {
|
|
14632
|
+
getDeployment: async (tenantId2, pluginType) => await getPluginDeploymentService()?.getDeployment(tenantId2, pluginType) ?? void 0,
|
|
14633
|
+
ensureRunning: async (tenantId2, pluginType) => {
|
|
14634
|
+
await getPluginDeploymentService()?.ensureRunning(tenantId2, pluginType);
|
|
14635
|
+
},
|
|
14636
|
+
baseUrl,
|
|
14637
|
+
serviceKey: process.env.MICROSANDBOX_API_KEY
|
|
14638
|
+
};
|
|
14639
|
+
}
|
|
14640
|
+
function buildExecutor(resolveSandbox) {
|
|
14641
|
+
const resident = buildResidentRouting();
|
|
14642
|
+
return new SandboxPluginExecutor({ resolveSandbox, ...resident ? { resident } : {} });
|
|
14643
|
+
}
|
|
14217
14644
|
function emptyCatalog() {
|
|
14218
14645
|
const resolveSandbox = async () => {
|
|
14219
14646
|
throw new Error("sandbox manager not configured");
|
|
14220
14647
|
};
|
|
14221
14648
|
return new TenantPluginCatalog({
|
|
14222
14649
|
resolveSandbox,
|
|
14223
|
-
executor:
|
|
14650
|
+
executor: buildExecutor(resolveSandbox),
|
|
14224
14651
|
reservedTypes: () => new Set(PluginRegistry.list())
|
|
14225
14652
|
});
|
|
14226
14653
|
}
|
|
@@ -14237,7 +14664,7 @@ function getSandboxPluginCatalog() {
|
|
|
14237
14664
|
const resolveSandbox = createSandboxResolver((config) => manager.getSandboxFromConfig(config));
|
|
14238
14665
|
catalog = new TenantPluginCatalog({
|
|
14239
14666
|
resolveSandbox,
|
|
14240
|
-
executor:
|
|
14667
|
+
executor: buildExecutor(resolveSandbox),
|
|
14241
14668
|
reservedTypes: () => new Set(PluginRegistry.list())
|
|
14242
14669
|
});
|
|
14243
14670
|
return catalog;
|
|
@@ -14384,23 +14811,23 @@ function toolName(tool68) {
|
|
|
14384
14811
|
return typeof tool68.name === "string" ? tool68.name : void 0;
|
|
14385
14812
|
}
|
|
14386
14813
|
function schemaHasConnectionKey(schema6) {
|
|
14387
|
-
const definition =
|
|
14814
|
+
const definition = isRecord8(schema6._def) ? schema6._def : void 0;
|
|
14388
14815
|
const shape = definition?.shape;
|
|
14389
14816
|
const fields = typeof shape === "function" ? shape() : shape;
|
|
14390
|
-
return
|
|
14817
|
+
return isRecord8(fields) && Object.prototype.hasOwnProperty.call(fields, "connectionKey");
|
|
14391
14818
|
}
|
|
14392
14819
|
function hasConnectionKeySchema(tool68) {
|
|
14393
|
-
if (!
|
|
14820
|
+
if (!isRecord8(tool68) || !isRecord8(tool68.schema)) return false;
|
|
14394
14821
|
if (schemaHasConnectionKey(tool68.schema)) return true;
|
|
14395
|
-
const definition =
|
|
14822
|
+
const definition = isRecord8(tool68.schema._def) ? tool68.schema._def : void 0;
|
|
14396
14823
|
if (Array.isArray(definition?.options) && definition.options.length > 0) {
|
|
14397
14824
|
return definition.options.every(
|
|
14398
|
-
(option) =>
|
|
14825
|
+
(option) => isRecord8(option) && schemaHasConnectionKey(option)
|
|
14399
14826
|
);
|
|
14400
14827
|
}
|
|
14401
14828
|
return false;
|
|
14402
14829
|
}
|
|
14403
|
-
function
|
|
14830
|
+
function isRecord8(value) {
|
|
14404
14831
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14405
14832
|
}
|
|
14406
14833
|
var CapabilityToolRegistry = class {
|
|
@@ -14492,17 +14919,17 @@ var CapabilityToolRegistry = class {
|
|
|
14492
14919
|
// src/middlewares/capabilityGuardMiddleware.ts
|
|
14493
14920
|
import { ToolMessage as ToolMessage5 } from "@langchain/core/messages";
|
|
14494
14921
|
import { createMiddleware as createMiddleware13 } from "langchain";
|
|
14495
|
-
function
|
|
14922
|
+
function isRecord9(value) {
|
|
14496
14923
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14497
14924
|
}
|
|
14498
14925
|
function capabilityRuntimeFromRequest(request) {
|
|
14499
|
-
if (!
|
|
14926
|
+
if (!isRecord9(request) || !isRecord9(request.runtime)) return void 0;
|
|
14500
14927
|
const configurable = request.runtime.configurable;
|
|
14501
14928
|
const context = request.runtime.context;
|
|
14502
|
-
const runConfig =
|
|
14929
|
+
const runConfig = isRecord9(configurable) && isRecord9(configurable.runConfig) ? configurable.runConfig : isRecord9(context) && isRecord9(context.runConfig) ? context.runConfig : void 0;
|
|
14503
14930
|
if (!runConfig) return void 0;
|
|
14504
14931
|
const value = runConfig.capabilityRuntime;
|
|
14505
|
-
if (!
|
|
14932
|
+
if (!isRecord9(value) || typeof value.revision !== "string" || !isRecord9(value.middleware)) {
|
|
14506
14933
|
return void 0;
|
|
14507
14934
|
}
|
|
14508
14935
|
return value;
|
|
@@ -14514,10 +14941,10 @@ function hasActiveRuntimePolicy(runtime) {
|
|
|
14514
14941
|
return Object.keys(runtime.middleware).length > 0;
|
|
14515
14942
|
}
|
|
14516
14943
|
function tenantIdFromRequest(request) {
|
|
14517
|
-
if (!
|
|
14944
|
+
if (!isRecord9(request) || !isRecord9(request.runtime)) return void 0;
|
|
14518
14945
|
const configurable = request.runtime.configurable;
|
|
14519
14946
|
const context = request.runtime.context;
|
|
14520
|
-
const runConfig =
|
|
14947
|
+
const runConfig = isRecord9(configurable) && isRecord9(configurable.runConfig) ? configurable.runConfig : isRecord9(context) && isRecord9(context.runConfig) ? context.runConfig : void 0;
|
|
14521
14948
|
return runConfig && typeof runConfig.tenantId === "string" ? runConfig.tenantId : void 0;
|
|
14522
14949
|
}
|
|
14523
14950
|
async function standardConnectionAllowed(request, capabilityType, config) {
|
|
@@ -14525,8 +14952,8 @@ async function standardConnectionAllowed(request, capabilityType, config) {
|
|
|
14525
14952
|
const hasConnectAll = Object.prototype.hasOwnProperty.call(config, "connectAll");
|
|
14526
14953
|
if (!hasConnections && (!hasConnectAll || config.connectAll !== true)) return false;
|
|
14527
14954
|
const tenantId2 = tenantIdFromRequest(request);
|
|
14528
|
-
const resource =
|
|
14529
|
-
const hasConnectionKey =
|
|
14955
|
+
const resource = isRecord9(request.toolCall) && isRecord9(request.toolCall.args) ? request.toolCall.args.connectionKey : void 0;
|
|
14956
|
+
const hasConnectionKey = isRecord9(request.toolCall) && isRecord9(request.toolCall.args) && Object.prototype.hasOwnProperty.call(
|
|
14530
14957
|
request.toolCall.args,
|
|
14531
14958
|
"connectionKey"
|
|
14532
14959
|
);
|
|
@@ -14592,10 +15019,10 @@ function createCapabilityGuardMiddleware(registry4) {
|
|
|
14592
15019
|
toolName: toolName2
|
|
14593
15020
|
});
|
|
14594
15021
|
}
|
|
14595
|
-
const standardConfig =
|
|
15022
|
+
const standardConfig = isRecord9(policy.config) ? policy.config : {};
|
|
14596
15023
|
if (registry4.isStandardConnectionTool(toolName2) && !await standardConnectionAllowed(request, metadata.capabilityType, standardConfig)) {
|
|
14597
15024
|
const args2 = request.toolCall.args;
|
|
14598
|
-
const resource =
|
|
15025
|
+
const resource = isRecord9(args2) && typeof args2.connectionKey === "string" ? args2.connectionKey : void 0;
|
|
14599
15026
|
return denialMessage(request.toolCall, {
|
|
14600
15027
|
code: "RESOURCE_NOT_ALLOWED",
|
|
14601
15028
|
message: typeof resource === "string" ? `Resource '${resource}' is not allowed by the capability policy` : "Resource selector 'connectionKey' must be a permitted string",
|
|
@@ -14608,8 +15035,8 @@ function createCapabilityGuardMiddleware(registry4) {
|
|
|
14608
15035
|
const selector = metadata.resourceSelector;
|
|
14609
15036
|
const args = request.toolCall.args;
|
|
14610
15037
|
if (selector) {
|
|
14611
|
-
const resource =
|
|
14612
|
-
const config =
|
|
15038
|
+
const resource = isRecord9(args) ? args[selector.argumentField] : void 0;
|
|
15039
|
+
const config = isRecord9(policy.config) ? policy.config : {};
|
|
14613
15040
|
const hasExplicitResources = Object.prototype.hasOwnProperty.call(config, selector.configField);
|
|
14614
15041
|
const allowsAll = !hasExplicitResources && selector.allowAllField !== void 0 && config[selector.allowAllField] === true;
|
|
14615
15042
|
const allowedResources = config[selector.configField];
|
|
@@ -30396,6 +30823,7 @@ function assistantToConfig(assistant) {
|
|
|
30396
30823
|
prompt: typeof assistant.graphDefinition === "string" ? assistant.graphDefinition : JSON.stringify(assistant.graphDefinition)
|
|
30397
30824
|
};
|
|
30398
30825
|
Object.assign(config, graphDef);
|
|
30826
|
+
if (assistant.ownerUserId !== void 0) config.ownerUserId = assistant.ownerUserId;
|
|
30399
30827
|
return config;
|
|
30400
30828
|
}
|
|
30401
30829
|
var _AgentLatticeManager = class _AgentLatticeManager extends BaseLatticeManager {
|
|
@@ -32788,6 +33216,9 @@ var MicrosandboxRemoteInstance = class {
|
|
|
32788
33216
|
}
|
|
32789
33217
|
return "unknown";
|
|
32790
33218
|
}
|
|
33219
|
+
async getEndpoint(port) {
|
|
33220
|
+
return this.client.getEndpoint(this.name, port);
|
|
33221
|
+
}
|
|
32791
33222
|
/**
|
|
32792
33223
|
* Pack a sandbox folder into a sibling ZIP through the microsandbox shell.
|
|
32793
33224
|
*
|
|
@@ -32854,6 +33285,19 @@ var MicrosandboxServiceClient = class {
|
|
|
32854
33285
|
method: "GET"
|
|
32855
33286
|
});
|
|
32856
33287
|
}
|
|
33288
|
+
/**
|
|
33289
|
+
* Resolve the endpoint for a published guest port of a sandbox.
|
|
33290
|
+
*
|
|
33291
|
+
* @param name - Sandbox name
|
|
33292
|
+
* @param port - Guest port published via `ensureSandbox({ exposePorts })`
|
|
33293
|
+
* @returns The scheme-less endpoint string and optional headers
|
|
33294
|
+
*/
|
|
33295
|
+
async getEndpoint(name, port) {
|
|
33296
|
+
return this.request(
|
|
33297
|
+
`/api/sandboxes/${encodeURIComponent(name)}/endpoints/${port}`,
|
|
33298
|
+
{ method: "GET" }
|
|
33299
|
+
);
|
|
33300
|
+
}
|
|
32857
33301
|
async readFile(sandboxName, path8) {
|
|
32858
33302
|
return this.request("/api/files/read", {
|
|
32859
33303
|
method: "POST",
|
|
@@ -33009,7 +33453,9 @@ var MicrosandboxServiceClient = class {
|
|
|
33009
33453
|
}
|
|
33010
33454
|
} catch {
|
|
33011
33455
|
}
|
|
33012
|
-
|
|
33456
|
+
const error = new Error(`Microsandbox service request failed: ${detail}`);
|
|
33457
|
+
error.statusCode = response.status;
|
|
33458
|
+
throw error;
|
|
33013
33459
|
}
|
|
33014
33460
|
const payload = await response.json();
|
|
33015
33461
|
if (payload?.success === false) {
|
|
@@ -33118,9 +33564,9 @@ var MicrosandboxRemoteProvider = class {
|
|
|
33118
33564
|
}
|
|
33119
33565
|
buildEnsureInput(config) {
|
|
33120
33566
|
const defaultMicrosandboxRemoteConfig = getDefaultMicrosandboxRemoteConfig();
|
|
33121
|
-
const image = this.config.image ?? defaultMicrosandboxRemoteConfig.image;
|
|
33567
|
+
const image = config.image ?? this.config.image ?? defaultMicrosandboxRemoteConfig.image;
|
|
33122
33568
|
console.log(
|
|
33123
|
-
`[MicrosandboxRemote] buildEnsureInput image=${image} from=${this.config.image ? "providerConfig" : "defaultConfig"}`
|
|
33569
|
+
`[MicrosandboxRemote] buildEnsureInput image=${image} from=${config.image ? "runConfig" : this.config.image ? "providerConfig" : "defaultConfig"}`
|
|
33124
33570
|
);
|
|
33125
33571
|
return {
|
|
33126
33572
|
image,
|
|
@@ -33130,7 +33576,10 @@ var MicrosandboxRemoteProvider = class {
|
|
|
33130
33576
|
...this.config.env,
|
|
33131
33577
|
...buildSandboxMetadataEnv(config)
|
|
33132
33578
|
},
|
|
33133
|
-
volumes: config?.volumes ?? this.buildDefaultVolumes(config)
|
|
33579
|
+
volumes: config?.volumes ?? this.buildDefaultVolumes(config),
|
|
33580
|
+
entrypoint: config.entrypoint,
|
|
33581
|
+
timeoutSeconds: config.timeoutSeconds,
|
|
33582
|
+
exposePorts: config.exposePorts
|
|
33134
33583
|
};
|
|
33135
33584
|
}
|
|
33136
33585
|
buildDefaultVolumes(config) {
|
|
@@ -34856,6 +35305,157 @@ async function connectAllChannels(getAdapter, options = {}) {
|
|
|
34856
35305
|
}
|
|
34857
35306
|
}
|
|
34858
35307
|
}
|
|
35308
|
+
async function disconnectAllChannels(getAdapter) {
|
|
35309
|
+
const store = getStoreLattice("default", "channelInstallation").store;
|
|
35310
|
+
const installations = await store.getAllInstallations();
|
|
35311
|
+
console.log(`[disconnectAllChannels] total=${installations.length} enabled=${installations.filter((i) => i.enabled).length}`);
|
|
35312
|
+
for (const inst of installations) {
|
|
35313
|
+
if (!inst.enabled) continue;
|
|
35314
|
+
const adapter = getAdapter(inst.channel);
|
|
35315
|
+
if (!adapter?.disconnect) continue;
|
|
35316
|
+
try {
|
|
35317
|
+
await adapter.disconnect(inst.id);
|
|
35318
|
+
} catch (error) {
|
|
35319
|
+
console.error(`[disconnectAllChannels] disconnect failed: channel=${inst.channel} tenant=${inst.tenantId} id=${inst.id}`, error);
|
|
35320
|
+
}
|
|
35321
|
+
}
|
|
35322
|
+
}
|
|
35323
|
+
|
|
35324
|
+
// src/channel/ChannelConnectionLeader.ts
|
|
35325
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
35326
|
+
var LEASE_KEY = "leader:channel-connections";
|
|
35327
|
+
var ChannelConnectionLeader = class {
|
|
35328
|
+
constructor(deps) {
|
|
35329
|
+
this.holder = null;
|
|
35330
|
+
this.connected = false;
|
|
35331
|
+
this.attempted = false;
|
|
35332
|
+
this.inFlight = null;
|
|
35333
|
+
this.timer = null;
|
|
35334
|
+
this.running = false;
|
|
35335
|
+
this.stopped = false;
|
|
35336
|
+
this.deps = deps;
|
|
35337
|
+
this.ttlMs = deps.ttlMs ?? 6e4;
|
|
35338
|
+
this.retryMs = deps.retryMs ?? Math.max(5e3, Math.floor(this.ttlMs / 3));
|
|
35339
|
+
}
|
|
35340
|
+
/** Starts the ownership loop (immediate tick, then every `retryMs`). */
|
|
35341
|
+
start() {
|
|
35342
|
+
if (this.timer || this.stopped) return;
|
|
35343
|
+
void this.tickOnce();
|
|
35344
|
+
this.timer = setInterval(() => {
|
|
35345
|
+
void this.tickOnce();
|
|
35346
|
+
}, this.retryMs);
|
|
35347
|
+
this.timer.unref?.();
|
|
35348
|
+
}
|
|
35349
|
+
/**
|
|
35350
|
+
* Releases ownership (if held) and disconnects.
|
|
35351
|
+
*
|
|
35352
|
+
* @returns A promise that settles after teardown completes.
|
|
35353
|
+
* @remarks Terminal: a stopped leader does not restart.
|
|
35354
|
+
*/
|
|
35355
|
+
async stop() {
|
|
35356
|
+
this.stopped = true;
|
|
35357
|
+
if (this.timer) {
|
|
35358
|
+
clearInterval(this.timer);
|
|
35359
|
+
this.timer = null;
|
|
35360
|
+
}
|
|
35361
|
+
if (this.inFlight) await this.inFlight.catch(() => void 0);
|
|
35362
|
+
if (this.holder) {
|
|
35363
|
+
const holder = this.holder;
|
|
35364
|
+
this.holder = null;
|
|
35365
|
+
await this.deps.leaseStore.release(LEASE_KEY, holder).catch((error) => {
|
|
35366
|
+
this.deps.log?.warn("Channel leader lease release failed", {
|
|
35367
|
+
error: error instanceof Error ? error.message : String(error)
|
|
35368
|
+
});
|
|
35369
|
+
});
|
|
35370
|
+
}
|
|
35371
|
+
await this.disconnectChannels();
|
|
35372
|
+
}
|
|
35373
|
+
/**
|
|
35374
|
+
* Runs one ownership/health step. Public for deterministic tests.
|
|
35375
|
+
*
|
|
35376
|
+
* @returns A promise that settles when this step completes.
|
|
35377
|
+
*/
|
|
35378
|
+
async tickOnce() {
|
|
35379
|
+
if (this.running || this.stopped) return;
|
|
35380
|
+
this.running = true;
|
|
35381
|
+
const run = this.runTick();
|
|
35382
|
+
this.inFlight = run;
|
|
35383
|
+
try {
|
|
35384
|
+
await run;
|
|
35385
|
+
} finally {
|
|
35386
|
+
if (this.inFlight === run) this.inFlight = null;
|
|
35387
|
+
this.running = false;
|
|
35388
|
+
}
|
|
35389
|
+
}
|
|
35390
|
+
/**
|
|
35391
|
+
* Performs one acquire/renew/step decision.
|
|
35392
|
+
*
|
|
35393
|
+
* @remarks A transient lease-store error while renewing keeps the current
|
|
35394
|
+
* holder and connections intact (retry next tick); only a confirmed
|
|
35395
|
+
* `renewed === false` triggers teardown.
|
|
35396
|
+
*/
|
|
35397
|
+
async runTick() {
|
|
35398
|
+
if (this.holder) {
|
|
35399
|
+
let renewed = false;
|
|
35400
|
+
let renewFailed = false;
|
|
35401
|
+
try {
|
|
35402
|
+
renewed = await this.deps.leaseStore.renew(LEASE_KEY, this.holder, this.ttlMs);
|
|
35403
|
+
} catch (error) {
|
|
35404
|
+
renewFailed = true;
|
|
35405
|
+
this.deps.log?.warn("Channel leader lease renew failed", {
|
|
35406
|
+
error: error instanceof Error ? error.message : String(error)
|
|
35407
|
+
});
|
|
35408
|
+
}
|
|
35409
|
+
if (renewFailed) return;
|
|
35410
|
+
if (renewed) {
|
|
35411
|
+
if (!this.connected) await this.connectChannels();
|
|
35412
|
+
return;
|
|
35413
|
+
}
|
|
35414
|
+
await this.loseLeadership();
|
|
35415
|
+
}
|
|
35416
|
+
await this.tryAcquire();
|
|
35417
|
+
}
|
|
35418
|
+
async tryAcquire() {
|
|
35419
|
+
const holder = `${this.deps.owner}#${randomUUID8()}`;
|
|
35420
|
+
const acquired = await this.deps.leaseStore.tryAcquire(LEASE_KEY, holder, this.ttlMs).catch((error) => {
|
|
35421
|
+
this.deps.log?.warn("Channel leader lease acquire failed", {
|
|
35422
|
+
error: error instanceof Error ? error.message : String(error)
|
|
35423
|
+
});
|
|
35424
|
+
return false;
|
|
35425
|
+
});
|
|
35426
|
+
if (!acquired) return;
|
|
35427
|
+
this.holder = holder;
|
|
35428
|
+
await this.connectChannels();
|
|
35429
|
+
}
|
|
35430
|
+
async loseLeadership() {
|
|
35431
|
+
this.holder = null;
|
|
35432
|
+
await this.disconnectChannels();
|
|
35433
|
+
}
|
|
35434
|
+
async connectChannels() {
|
|
35435
|
+
this.attempted = true;
|
|
35436
|
+
try {
|
|
35437
|
+
await this.deps.connect();
|
|
35438
|
+
this.connected = true;
|
|
35439
|
+
} catch (error) {
|
|
35440
|
+
this.connected = false;
|
|
35441
|
+
this.deps.log?.warn("Channel leader connect failed", {
|
|
35442
|
+
error: error instanceof Error ? error.message : String(error)
|
|
35443
|
+
});
|
|
35444
|
+
}
|
|
35445
|
+
}
|
|
35446
|
+
async disconnectChannels() {
|
|
35447
|
+
if (!this.attempted) return;
|
|
35448
|
+
this.attempted = false;
|
|
35449
|
+
this.connected = false;
|
|
35450
|
+
try {
|
|
35451
|
+
await this.deps.disconnect();
|
|
35452
|
+
} catch (error) {
|
|
35453
|
+
this.deps.log?.warn("Channel leader disconnect failed", {
|
|
35454
|
+
error: error instanceof Error ? error.message : String(error)
|
|
35455
|
+
});
|
|
35456
|
+
}
|
|
35457
|
+
}
|
|
35458
|
+
};
|
|
34859
35459
|
|
|
34860
35460
|
// src/eval_lattice/LatticeEval.ts
|
|
34861
35461
|
import { HumanMessage as HumanMessage5 } from "@langchain/core/messages";
|
|
@@ -36304,6 +36904,7 @@ var OutboxPoller = class {
|
|
|
36304
36904
|
};
|
|
36305
36905
|
|
|
36306
36906
|
// src/services/ProjectMutationMutex.ts
|
|
36907
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
|
|
36307
36908
|
import { snapshotExactRecord as snapshotExactRecord4 } from "@axiom-lattice/protocols";
|
|
36308
36909
|
|
|
36309
36910
|
// src/services/coordinationLease.ts
|
|
@@ -36316,7 +36917,7 @@ function getCoordinationLeaseStore() {
|
|
|
36316
36917
|
}
|
|
36317
36918
|
|
|
36318
36919
|
// src/services/withLease.ts
|
|
36319
|
-
import { randomUUID as
|
|
36920
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
36320
36921
|
var LeaseAcquisitionTimeoutError = class extends Error {
|
|
36321
36922
|
/**
|
|
36322
36923
|
* @param key Lease key that could not be acquired.
|
|
@@ -36341,7 +36942,7 @@ async function withLease(store, key5, ownerLabel, fn, options = {}) {
|
|
|
36341
36942
|
const acquireTimeoutMs = options.acquireTimeoutMs ?? 1e4;
|
|
36342
36943
|
const retryDelayMs = options.retryDelayMs ?? 50;
|
|
36343
36944
|
const heartbeatMs = options.heartbeatMs ?? Math.max(1e3, Math.floor(ttlMs / 3));
|
|
36344
|
-
const holder = `${ownerLabel}#${
|
|
36945
|
+
const holder = `${ownerLabel}#${randomUUID9()}`;
|
|
36345
36946
|
const deadline = Date.now() + acquireTimeoutMs;
|
|
36346
36947
|
for (; ; ) {
|
|
36347
36948
|
if (await store.tryAcquire(key5, holder, ttlMs)) break;
|
|
@@ -36373,16 +36974,24 @@ var ProjectMutationMutex = class {
|
|
|
36373
36974
|
constructor() {
|
|
36374
36975
|
this.queues = /* @__PURE__ */ new Map();
|
|
36375
36976
|
this.active = /* @__PURE__ */ new Set();
|
|
36977
|
+
/** Project keys already held by the current async context (reentrancy). */
|
|
36978
|
+
this.held = new AsyncLocalStorage2();
|
|
36376
36979
|
}
|
|
36377
36980
|
/** Runs one mutation after all earlier mutations for the same Tenant and Project. */
|
|
36378
36981
|
async runExclusive(scope, operation, signal) {
|
|
36379
36982
|
const key5 = projectKey(scope);
|
|
36983
|
+
const inherited = this.held.getStore();
|
|
36984
|
+
if (inherited?.has(key5)) return operation();
|
|
36380
36985
|
const leaseKey = `project-mutation:${encodeURIComponent(scope.tenantId)}:${encodeURIComponent(scope.projectId)}`;
|
|
36381
36986
|
await this.acquire(key5, signal);
|
|
36987
|
+
const held = new Set(inherited);
|
|
36988
|
+
held.add(key5);
|
|
36382
36989
|
try {
|
|
36383
|
-
|
|
36384
|
-
|
|
36385
|
-
|
|
36990
|
+
return await this.held.run(held, async () => {
|
|
36991
|
+
const store = getCoordinationLeaseStore();
|
|
36992
|
+
if (!store) return await operation();
|
|
36993
|
+
return await withLease(store, leaseKey, getReplicaId(), operation, MUTATION_LEASE_OPTIONS);
|
|
36994
|
+
});
|
|
36386
36995
|
} finally {
|
|
36387
36996
|
this.release(key5);
|
|
36388
36997
|
}
|
|
@@ -36440,7 +37049,7 @@ function createTrustedAgentMessageDispatcher(resolve4) {
|
|
|
36440
37049
|
}
|
|
36441
37050
|
|
|
36442
37051
|
// src/services/RoomAgentMessageService.ts
|
|
36443
|
-
import { randomUUID as
|
|
37052
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
36444
37053
|
import { snapshotExactArray, snapshotExactRecord as snapshotExactRecord5 } from "@axiom-lattice/protocols";
|
|
36445
37054
|
var INPUT_KEYS = [
|
|
36446
37055
|
"tenantId",
|
|
@@ -36456,6 +37065,7 @@ var INPUT_KEYS = [
|
|
|
36456
37065
|
];
|
|
36457
37066
|
var REPLY_INPUT_KEYS = ["tenantId", "membershipId", "text", "sourceRoomMessageId", "inputMessageId", "sourceId", "idempotencyKey"];
|
|
36458
37067
|
var INTERRUPT_INPUT_KEYS = ["tenantId", "membershipId", "text", "sourceRoomMessageId", "inputMessageId"];
|
|
37068
|
+
var CORRELATED_INTERRUPT_INPUT_KEYS = ["tenantId", "membershipId", "correlationKey", "text"];
|
|
36459
37069
|
var RoomAgentMessageService = class {
|
|
36460
37070
|
/**
|
|
36461
37071
|
* Creates a room writer over the durable membership and message stores.
|
|
@@ -36503,6 +37113,50 @@ var RoomAgentMessageService = class {
|
|
|
36503
37113
|
idempotencyKey: `room-interrupt:${snapshot.sourceRoomMessageId}:${snapshot.membershipId}:${sourceId}`
|
|
36504
37114
|
});
|
|
36505
37115
|
}
|
|
37116
|
+
/**
|
|
37117
|
+
* Persists a correlated HITL interrupt that has no human source message.
|
|
37118
|
+
*
|
|
37119
|
+
* @param input Explicit tenant, active membership, caller-supplied correlation key, and text.
|
|
37120
|
+
* @returns A deep clone of the canonical idempotent room message.
|
|
37121
|
+
* @remarks Business-agnostic: the caller owns the correlation key. Scope is
|
|
37122
|
+
* validated against the durable membership; no source message is required.
|
|
37123
|
+
*/
|
|
37124
|
+
postCorrelatedInterrupt(input) {
|
|
37125
|
+
const snapshot = exactStringRecord(input, CORRELATED_INTERRUPT_INPUT_KEYS, "Invalid project room interrupt input");
|
|
37126
|
+
return this.persistScoped({
|
|
37127
|
+
tenantId: snapshot.tenantId,
|
|
37128
|
+
membershipId: snapshot.membershipId,
|
|
37129
|
+
text: snapshot.text,
|
|
37130
|
+
sourceId: `interrupt:${snapshot.correlationKey}`,
|
|
37131
|
+
idempotencyKey: `room-interrupt:${snapshot.membershipId}:${snapshot.correlationKey}`
|
|
37132
|
+
});
|
|
37133
|
+
}
|
|
37134
|
+
async persistScoped(input) {
|
|
37135
|
+
const text = input.text.trim();
|
|
37136
|
+
if (text.length < 1 || text.length > 2e4) throw new Error("Invalid project room message text");
|
|
37137
|
+
const member = snapshotMembership(await this.deps.memberships.findById(input.tenantId, input.membershipId));
|
|
37138
|
+
if (!member || member.id !== input.membershipId || member.status !== "active" || member.tenantId !== input.tenantId) {
|
|
37139
|
+
throw new Error("Invalid project room membership");
|
|
37140
|
+
}
|
|
37141
|
+
const candidate = {
|
|
37142
|
+
id: (this.deps.idFactory ?? randomUUID10)(),
|
|
37143
|
+
tenantId: member.tenantId,
|
|
37144
|
+
workspaceId: member.workspaceId,
|
|
37145
|
+
projectId: member.projectId,
|
|
37146
|
+
roomId: member.roomId,
|
|
37147
|
+
author: { type: "bot", membershipId: member.id, assistantId: member.assistantId },
|
|
37148
|
+
content: { type: "text", text },
|
|
37149
|
+
mentions: [],
|
|
37150
|
+
source: "agent",
|
|
37151
|
+
sourceId: input.sourceId,
|
|
37152
|
+
idempotencyKey: input.idempotencyKey
|
|
37153
|
+
};
|
|
37154
|
+
const canonical = snapshotCanonicalScoped(await this.deps.messages.createIdempotent(candidate), candidate);
|
|
37155
|
+
if (!canonical) throw new Error("Invalid canonical project room message");
|
|
37156
|
+
const result = structuredClone(canonical);
|
|
37157
|
+
this.notifyCommitted(result);
|
|
37158
|
+
return result;
|
|
37159
|
+
}
|
|
36506
37160
|
async persistDerived(input) {
|
|
36507
37161
|
const source = snapshotSource(await this.deps.messages.findById(input.tenantId, input.sourceRoomMessageId));
|
|
36508
37162
|
if (!source || source.id !== input.sourceRoomMessageId || source.tenantId !== input.tenantId) throw new Error("Invalid project room message");
|
|
@@ -36528,7 +37182,7 @@ var RoomAgentMessageService = class {
|
|
|
36528
37182
|
throw new Error("Invalid project room membership");
|
|
36529
37183
|
}
|
|
36530
37184
|
const candidate = {
|
|
36531
|
-
id: (this.deps.idFactory ??
|
|
37185
|
+
id: (this.deps.idFactory ?? randomUUID10)(),
|
|
36532
37186
|
tenantId: input.tenantId,
|
|
36533
37187
|
workspaceId: input.workspaceId,
|
|
36534
37188
|
projectId: input.projectId,
|
|
@@ -36658,6 +37312,27 @@ function snapshotCanonical(value, expected) {
|
|
|
36658
37312
|
createdAt
|
|
36659
37313
|
};
|
|
36660
37314
|
}
|
|
37315
|
+
function snapshotCanonicalScoped(value, expected) {
|
|
37316
|
+
const values = exactValues(value, ["id", "tenantId", "workspaceId", "projectId", "roomId", "author", "content", "mentions", "source", "sourceId", "idempotencyKey", "createdAt"], ["replyToMessageId"]);
|
|
37317
|
+
const author = exactValues(values?.author, ["type", "membershipId", "assistantId"]);
|
|
37318
|
+
const content = exactValues(values?.content, ["type", "text"]);
|
|
37319
|
+
const createdAt = date(values?.createdAt);
|
|
37320
|
+
if (!values || !createdAt || typeof values.id !== "string" || values.id.length === 0 || 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;
|
|
37321
|
+
return {
|
|
37322
|
+
id: values.id,
|
|
37323
|
+
tenantId: expected.tenantId,
|
|
37324
|
+
workspaceId: expected.workspaceId,
|
|
37325
|
+
projectId: expected.projectId,
|
|
37326
|
+
roomId: expected.roomId,
|
|
37327
|
+
author: { type: "bot", membershipId: expected.author.membershipId, assistantId: expected.author.assistantId },
|
|
37328
|
+
content: { type: "text", text: expected.content.text },
|
|
37329
|
+
mentions: [],
|
|
37330
|
+
source: "agent",
|
|
37331
|
+
sourceId: expected.sourceId,
|
|
37332
|
+
idempotencyKey: expected.idempotencyKey,
|
|
37333
|
+
createdAt
|
|
37334
|
+
};
|
|
37335
|
+
}
|
|
36661
37336
|
|
|
36662
37337
|
// src/services/ProjectTaskOwnerPolicy.ts
|
|
36663
37338
|
import { snapshotExactRecord as snapshotExactRecord6 } from "@axiom-lattice/protocols";
|
|
@@ -39115,7 +39790,7 @@ function createTaskLifecycleService(deps) {
|
|
|
39115
39790
|
}
|
|
39116
39791
|
|
|
39117
39792
|
// src/services/TaskExecutionService.ts
|
|
39118
|
-
import { randomUUID as
|
|
39793
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
39119
39794
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
39120
39795
|
import { isExecutionResultEventKey as isExecutionResultEventKey2 } from "@axiom-lattice/protocols";
|
|
39121
39796
|
|
|
@@ -39438,13 +40113,13 @@ async function reconcileAllTenantTaskRuns(input) {
|
|
|
39438
40113
|
}
|
|
39439
40114
|
|
|
39440
40115
|
// src/services/taskRunLease.ts
|
|
39441
|
-
import { randomUUID as
|
|
40116
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
39442
40117
|
function taskRunLeaseKey(taskId) {
|
|
39443
40118
|
return `task-run:${taskId}`;
|
|
39444
40119
|
}
|
|
39445
40120
|
async function tryAcquireTaskRun(input) {
|
|
39446
40121
|
const key5 = taskRunLeaseKey(input.taskId);
|
|
39447
|
-
const holder = `${input.owner}#${
|
|
40122
|
+
const holder = `${input.owner}#${randomUUID11()}`;
|
|
39448
40123
|
if (!await input.store.tryAcquire(key5, holder, input.ttlMs)) return null;
|
|
39449
40124
|
const heartbeatMs = input.heartbeatMs ?? Math.max(1e3, Math.floor(input.ttlMs / 3));
|
|
39450
40125
|
let released = false;
|
|
@@ -39811,11 +40486,11 @@ function createTaskExecutionService(deps) {
|
|
|
39811
40486
|
let threadId;
|
|
39812
40487
|
let runId;
|
|
39813
40488
|
try {
|
|
39814
|
-
threadId = storedThreadId ?? (deps.createThreadId ??
|
|
40489
|
+
threadId = storedThreadId ?? (deps.createThreadId ?? randomUUID12)();
|
|
39815
40490
|
if (task.status === "interrupted" && reuseCurrentRun) {
|
|
39816
40491
|
runId = storedRunId;
|
|
39817
40492
|
} else if (task.status === "interrupted" || task.status === "failed") {
|
|
39818
|
-
runId = (deps.createRunId ??
|
|
40493
|
+
runId = (deps.createRunId ?? randomUUID12)();
|
|
39819
40494
|
}
|
|
39820
40495
|
} catch (error) {
|
|
39821
40496
|
return {
|
|
@@ -40074,11 +40749,11 @@ function createTaskExecutionService(deps) {
|
|
|
40074
40749
|
}
|
|
40075
40750
|
|
|
40076
40751
|
// src/services/leadership.ts
|
|
40077
|
-
import { randomUUID as
|
|
40752
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
40078
40753
|
async function runAsLeader(store, name, fn, options = {}) {
|
|
40079
40754
|
const key5 = `leader:${name}`;
|
|
40080
40755
|
const owner2 = options.owner ?? getReplicaId();
|
|
40081
|
-
const holder = `${owner2}#${
|
|
40756
|
+
const holder = `${owner2}#${randomUUID13()}`;
|
|
40082
40757
|
const ttlMs = options.ttlMs ?? 6e4;
|
|
40083
40758
|
if (!await store.tryAcquire(key5, holder, ttlMs)) return { ran: false };
|
|
40084
40759
|
const heartbeat = setInterval(() => {
|
|
@@ -41311,16 +41986,16 @@ function createAskUserToClarifyTool() {
|
|
|
41311
41986
|
}
|
|
41312
41987
|
|
|
41313
41988
|
// src/middlewares/askUserClarifyMiddleware.ts
|
|
41314
|
-
function
|
|
41989
|
+
function isRecord10(value) {
|
|
41315
41990
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
41316
41991
|
}
|
|
41317
41992
|
function isProjectRoomRun(request) {
|
|
41318
|
-
if (!
|
|
41993
|
+
if (!isRecord10(request) || !isRecord10(request.runtime)) return false;
|
|
41319
41994
|
const runtime = request.runtime;
|
|
41320
|
-
const configurable =
|
|
41321
|
-
const context =
|
|
41322
|
-
const runConfig =
|
|
41323
|
-
return
|
|
41995
|
+
const configurable = isRecord10(runtime.configurable) ? runtime.configurable.runConfig : void 0;
|
|
41996
|
+
const context = isRecord10(runtime.context) ? runtime.context.runConfig : void 0;
|
|
41997
|
+
const runConfig = isRecord10(configurable) ? configurable : isRecord10(context) ? context : void 0;
|
|
41998
|
+
return isRecord10(runConfig?.projectRoom);
|
|
41324
41999
|
}
|
|
41325
42000
|
function createAskUserClarifyMiddleware() {
|
|
41326
42001
|
return createMiddleware21({
|
|
@@ -42765,15 +43440,16 @@ function executorString(value) {
|
|
|
42765
43440
|
}
|
|
42766
43441
|
async function renderAvailableExecutors(rc, tenantId2) {
|
|
42767
43442
|
const authority = classifyTaskAuthority(rc);
|
|
42768
|
-
const
|
|
42769
|
-
const
|
|
42770
|
-
const
|
|
43443
|
+
const trusted = authority.kind === "projectRoom" || authority.kind === "projectTask" ? authority.trusted : void 0;
|
|
43444
|
+
const useRoster = trusted !== void 0 && !trustedRosterExempt(trusted);
|
|
43445
|
+
const projectId = trusted ? (trusted.projectTask ?? trusted.projectRoom)?.projectId : void 0;
|
|
43446
|
+
const cacheKey = `${tenantId2}:${useRoster ? `project:${projectId ?? ""}` : "tenant"}`;
|
|
42771
43447
|
const cached2 = executorCache.get(cacheKey);
|
|
42772
43448
|
const now = Date.now();
|
|
42773
43449
|
if (cached2 && now - cached2.at < EXECUTOR_CACHE_TTL_MS) return cached2.text;
|
|
42774
43450
|
const lines = [];
|
|
42775
43451
|
try {
|
|
42776
|
-
if (
|
|
43452
|
+
if (useRoster && projectId) {
|
|
42777
43453
|
const store = getStoreLattice("default", "projectBotMembership").store;
|
|
42778
43454
|
if (typeof store.list === "function") {
|
|
42779
43455
|
const bots = await store.list(tenantId2, projectId);
|
|
@@ -42788,11 +43464,15 @@ async function renderAvailableExecutors(rc, tenantId2) {
|
|
|
42788
43464
|
}
|
|
42789
43465
|
}
|
|
42790
43466
|
} else {
|
|
43467
|
+
const exempt = trusted !== void 0 && trustedRosterExempt(trusted) ? trusted : void 0;
|
|
43468
|
+
const excluded = exempt ? await excludedRosterExecutors(exempt) : /* @__PURE__ */ new Set();
|
|
42791
43469
|
const configs = await agentLatticeManager.getAllAgentConfigsByTenant(tenantId2);
|
|
42792
43470
|
for (const config of configs.slice(0, 40)) {
|
|
42793
43471
|
const row = config;
|
|
42794
43472
|
const id = executorString(row.key);
|
|
42795
43473
|
if (!id) continue;
|
|
43474
|
+
if (row.ownerUserId !== void 0) continue;
|
|
43475
|
+
if (excluded.has(id)) continue;
|
|
42796
43476
|
const name = executorString(row.name);
|
|
42797
43477
|
const description = executorString(row.description);
|
|
42798
43478
|
lines.push(`- ${id}${name ? ` \u2014 ${name}` : ""}${description ? ` (${description.slice(0, 120)})` : ""}`);
|
|
@@ -42804,7 +43484,7 @@ async function renderAvailableExecutors(rc, tenantId2) {
|
|
|
42804
43484
|
executorCache.set(cacheKey, { at: now, text: "" });
|
|
42805
43485
|
return "";
|
|
42806
43486
|
}
|
|
42807
|
-
const header =
|
|
43487
|
+
const header = useRoster ? "## Available task executors (Project roster)" : "## Available task executors";
|
|
42808
43488
|
const text = `
|
|
42809
43489
|
|
|
42810
43490
|
${header}
|
|
@@ -42816,6 +43496,22 @@ ${lines.join("\n")}
|
|
|
42816
43496
|
executorCache.set(cacheKey, { at: now, text });
|
|
42817
43497
|
return text;
|
|
42818
43498
|
}
|
|
43499
|
+
async function excludedRosterExecutors(trusted) {
|
|
43500
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
43501
|
+
const context = trusted.projectTask ?? trusted.projectRoom;
|
|
43502
|
+
if (!context) return excluded;
|
|
43503
|
+
try {
|
|
43504
|
+
const store = getStoreLattice("default", "projectBotMembership").store;
|
|
43505
|
+
if (typeof store.list !== "function") return excluded;
|
|
43506
|
+
const rows = await store.list(context.tenantId, context.projectId);
|
|
43507
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
43508
|
+
const record = row;
|
|
43509
|
+
if (record.status !== "active" && typeof record.assistantId === "string") excluded.add(record.assistantId);
|
|
43510
|
+
}
|
|
43511
|
+
} catch {
|
|
43512
|
+
}
|
|
43513
|
+
return excluded;
|
|
43514
|
+
}
|
|
42819
43515
|
function delegatedTaskScopeError(taskId) {
|
|
42820
43516
|
return JSON.stringify({
|
|
42821
43517
|
success: false,
|
|
@@ -42865,7 +43561,7 @@ function trustedRosterScope(trusted) {
|
|
|
42865
43561
|
};
|
|
42866
43562
|
}
|
|
42867
43563
|
function trustedRosterExempt(trusted) {
|
|
42868
|
-
return trusted.projectTask?.rosterExempt === true;
|
|
43564
|
+
return trusted.projectTask?.rosterExempt === true || trusted.projectRoom?.rosterExempt === true;
|
|
42869
43565
|
}
|
|
42870
43566
|
async function requireTrustedOwner(trusted, targetAssistantId) {
|
|
42871
43567
|
const context = trusted.projectTask ?? trusted.projectRoom;
|
|
@@ -42881,6 +43577,17 @@ async function requireTrustedOwner(trusted, targetAssistantId) {
|
|
|
42881
43577
|
targetAssistantId
|
|
42882
43578
|
});
|
|
42883
43579
|
}
|
|
43580
|
+
async function rosterExemptOwnerError(tenantId2, targetAssistantId) {
|
|
43581
|
+
try {
|
|
43582
|
+
const lattice = await agentLatticeManager.getOrLoadWithTenant(tenantId2, targetAssistantId);
|
|
43583
|
+
if (lattice && lattice.config.ownerUserId === void 0) return void 0;
|
|
43584
|
+
} catch {
|
|
43585
|
+
}
|
|
43586
|
+
return projectPolicyError(new ProjectTaskOwnerPolicyError(
|
|
43587
|
+
"PROJECT_TASK_OWNER_INACTIVE",
|
|
43588
|
+
"Target Project task owner is not a shared agent of this tenant."
|
|
43589
|
+
));
|
|
43590
|
+
}
|
|
42884
43591
|
function trustedTaskContextFailure(task, trusted) {
|
|
42885
43592
|
if (task.tenantId === trusted.tenantId && task.workspaceId === trusted.workspaceId && task.projectId === trusted.projectId) return void 0;
|
|
42886
43593
|
return JSON.stringify({
|
|
@@ -43485,6 +44192,12 @@ function createTaskMiddleware(options = {}) {
|
|
|
43485
44192
|
"Target Project task owner is not active in the exact roster."
|
|
43486
44193
|
));
|
|
43487
44194
|
}
|
|
44195
|
+
} else {
|
|
44196
|
+
const ownerError = await rosterExemptOwnerError(
|
|
44197
|
+
(trustedProject.projectTask ?? trustedProject.projectRoom)?.tenantId ?? tenantId2,
|
|
44198
|
+
ownerId
|
|
44199
|
+
);
|
|
44200
|
+
if (ownerError) return ownerError;
|
|
43488
44201
|
}
|
|
43489
44202
|
}
|
|
43490
44203
|
if (!delegatedTaskId && (typeof effectiveOwnerId !== "string" || effectiveOwnerId.length === 0)) {
|
|
@@ -43546,6 +44259,12 @@ function createTaskMiddleware(options = {}) {
|
|
|
43546
44259
|
} catch (error) {
|
|
43547
44260
|
return projectPolicyError(error);
|
|
43548
44261
|
}
|
|
44262
|
+
} else {
|
|
44263
|
+
const ownerError = await rosterExemptOwnerError(
|
|
44264
|
+
(trustedProject.projectTask ?? trustedProject.projectRoom)?.tenantId ?? tenantId2,
|
|
44265
|
+
ownerId
|
|
44266
|
+
);
|
|
44267
|
+
if (ownerError) return ownerError;
|
|
43549
44268
|
}
|
|
43550
44269
|
}
|
|
43551
44270
|
return store.create({
|
|
@@ -44593,7 +45312,7 @@ function createTaskMiddleware(options = {}) {
|
|
|
44593
45312
|
}
|
|
44594
45313
|
const callerAssistantId = rc.assistant_id;
|
|
44595
45314
|
const agentId = input.agentId === "general-purpose" && callerAssistantId ? `${callerAssistantId}-general-purpose` : input.agentId;
|
|
44596
|
-
const
|
|
45315
|
+
const service3 = createTaskExecutionService({
|
|
44597
45316
|
taskStore: getTaskStore(),
|
|
44598
45317
|
workItemStore: getTaskWorkItemStore(),
|
|
44599
45318
|
resolveAgent: async (targetTenantId, agentId2) => !!await agentLatticeManager.getOrLoadWithTenant(targetTenantId, agentId2),
|
|
@@ -44618,7 +45337,7 @@ function createTaskMiddleware(options = {}) {
|
|
|
44618
45337
|
}
|
|
44619
45338
|
});
|
|
44620
45339
|
const trustedCaller = authority.kind === "projectTask" || authority.kind === "projectRoom" ? authority.trusted.projectTask ?? authority.trusted.projectRoom : void 0;
|
|
44621
|
-
return JSON.stringify(await
|
|
45340
|
+
return JSON.stringify(await service3.start({
|
|
44622
45341
|
tenantId: tenantId2,
|
|
44623
45342
|
workspaceId: rc.workspaceId,
|
|
44624
45343
|
projectId: rc.projectId,
|
|
@@ -46140,6 +46859,24 @@ function snapshotMembership3(value) {
|
|
|
46140
46859
|
function snapshotRows(value) {
|
|
46141
46860
|
return snapshotExactArray2(value) ?? [];
|
|
46142
46861
|
}
|
|
46862
|
+
async function rosterExemptMembershipProjection(scope) {
|
|
46863
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
46864
|
+
const roles = /* @__PURE__ */ new Map();
|
|
46865
|
+
try {
|
|
46866
|
+
const rows = snapshotRows(await getStoreLattice("default", "projectBotMembership").store.list(scope.tenantId, scope.projectId));
|
|
46867
|
+
for (const row of rows) {
|
|
46868
|
+
const member = snapshotMembership3(row);
|
|
46869
|
+
if (!member) continue;
|
|
46870
|
+
if (member.status !== "active") {
|
|
46871
|
+
excluded.add(member.assistantId);
|
|
46872
|
+
continue;
|
|
46873
|
+
}
|
|
46874
|
+
roles.set(member.assistantId, member.role);
|
|
46875
|
+
}
|
|
46876
|
+
} catch {
|
|
46877
|
+
}
|
|
46878
|
+
return { excluded, roles };
|
|
46879
|
+
}
|
|
46143
46880
|
async function hasActiveCurrentMembership(scope) {
|
|
46144
46881
|
const memberships = getStoreLattice("default", "projectBotMembership").store;
|
|
46145
46882
|
const member = snapshotMembership3(await memberships.findById(scope.tenantId, scope.membershipId));
|
|
@@ -46156,11 +46893,11 @@ function createProjectRoomMiddleware() {
|
|
|
46156
46893
|
if (!scope) return CONTEXT_REQUIRED;
|
|
46157
46894
|
const digest = createHash7("sha256").update(text).digest("hex");
|
|
46158
46895
|
try {
|
|
46159
|
-
const
|
|
46896
|
+
const service3 = new RoomAgentMessageService({
|
|
46160
46897
|
memberships: getStoreLattice("default", "projectBotMembership").store,
|
|
46161
46898
|
messages: getStoreLattice("default", "projectRoomMessage").store
|
|
46162
46899
|
});
|
|
46163
|
-
await
|
|
46900
|
+
await service3.post({
|
|
46164
46901
|
tenantId: scope.tenantId,
|
|
46165
46902
|
workspaceId: scope.workspaceId,
|
|
46166
46903
|
projectId: scope.projectId,
|
|
@@ -46186,6 +46923,25 @@ function createProjectRoomMiddleware() {
|
|
|
46186
46923
|
if (!scope) return CONTEXT_REQUIRED;
|
|
46187
46924
|
try {
|
|
46188
46925
|
if (!await hasActiveCurrentMembership(scope)) return CONTEXT_REQUIRED;
|
|
46926
|
+
if (scope.rosterExempt === true) {
|
|
46927
|
+
const { excluded, roles } = await rosterExemptMembershipProjection(scope);
|
|
46928
|
+
const configs = await agentLatticeManager.getAllAgentConfigsByTenant(scope.tenantId);
|
|
46929
|
+
const members2 = configs.filter((entry) => entry.ownerUserId === void 0).filter((entry) => {
|
|
46930
|
+
const key5 = entry.key;
|
|
46931
|
+
return typeof key5 === "string" && !excluded.has(key5);
|
|
46932
|
+
}).map((entry) => {
|
|
46933
|
+
const row = entry;
|
|
46934
|
+
const name = typeof row.name === "string" ? row.name : "";
|
|
46935
|
+
const assistantId = typeof row.key === "string" ? row.key : "";
|
|
46936
|
+
return {
|
|
46937
|
+
assistantId,
|
|
46938
|
+
mentionName: name,
|
|
46939
|
+
title: name,
|
|
46940
|
+
role: roles.get(assistantId) ?? "specialist"
|
|
46941
|
+
};
|
|
46942
|
+
}).filter((member) => member.assistantId !== "" && member.mentionName !== "").sort((left, right) => left.assistantId < right.assistantId ? -1 : left.assistantId > right.assistantId ? 1 : 0);
|
|
46943
|
+
return JSON.stringify({ success: true, members: members2 });
|
|
46944
|
+
}
|
|
46189
46945
|
const rows = await getStoreLattice("default", "projectBotMembership").store.list(scope.tenantId, scope.projectId);
|
|
46190
46946
|
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) => ({
|
|
46191
46947
|
assistantId: member.assistantId,
|
|
@@ -46456,6 +47212,18 @@ On top of that, add any of three combinable classes:
|
|
|
46456
47212
|
## Combinations
|
|
46457
47213
|
A plugin is the SET of classes it needs, e.g. \`connection + tools + skills\` or \`UI + connection\`.
|
|
46458
47214
|
|
|
47215
|
+
## Scenarios \u2192 skills
|
|
47216
|
+
| Scenario | Read |
|
|
47217
|
+
|---|---|
|
|
47218
|
+
| Tool that returns/transforms data | \`sandbox-plugins-manifest\` + \`sandbox-plugins-handlers\` |
|
|
47219
|
+
| Skills-only (procedural knowledge, no tools) | \`sandbox-plugins-authoring\` (skills) |
|
|
47220
|
+
| Tool needs credentials / resource discovery | \`sandbox-plugins-connection\` |
|
|
47221
|
+
| Tool shows an interactive panel | \`sandbox-plugins-ui\` |
|
|
47222
|
+
| Tool orchestrates platform tools/agents | \`sandbox-plugins-consume-open\` |
|
|
47223
|
+
| Tool callable by external MCP clients | \`sandbox-plugins-expose\` |
|
|
47224
|
+
| Run as a long-lived resident service | \`sandbox-plugins-deploy\` |
|
|
47225
|
+
| Validate / test / debug | \`sandbox-plugins-testing\` |
|
|
47226
|
+
|
|
46459
47227
|
## Agents (not a supported authoring path)
|
|
46460
47228
|
The manifest schema accepts an \`agents\` block and tenant plugin agents are
|
|
46461
47229
|
registered, but building agents this way is not supported or designed for tenant
|
|
@@ -46666,10 +47434,38 @@ lifecycle handshake:
|
|
|
46666
47434
|
- \`ui/notifications/tool-result\` \u2192 a \`CallToolResult\` \`{ content: [{ type: "text", text }] }\` (the tool output text, with any \`mcp_app\` fence stripped).
|
|
46667
47435
|
|
|
46668
47436
|
Register the \`message\` listener **before** \`ui/initialize\` so nothing is missed.
|
|
46669
|
-
|
|
46670
|
-
|
|
46671
|
-
|
|
46672
|
-
|
|
47437
|
+
Read the payload from \`params.content[0].text\` \u2014 the notification is a
|
|
47438
|
+
\`CallToolResult\`, **not** your handler payload at the top level. The result
|
|
47439
|
+
carries **TEXT content only** \u2014 no \`structuredContent\` or \`artifact\`; parse the
|
|
47440
|
+
JSON yourself if the tool emitted JSON (it is the handler's \`result\`, so the
|
|
47441
|
+
handler must wrap it under \`result\`). To re-query or refresh, call \`tools/call\`
|
|
47442
|
+
from the View and render its response (the host does not re-push \`tool-result\`
|
|
47443
|
+
for your own calls).
|
|
47444
|
+
|
|
47445
|
+
When you call \`tools/call\` from the View, the JSON-RPC response \`result\` is the
|
|
47446
|
+
handler's own \`result\` value **directly** \u2014 it is **not wrapped in a**
|
|
47447
|
+
\`CallToolResult\` \`{ content: [...] }\` (that wrapper is only used for the host's
|
|
47448
|
+
\`tool-result\` push). Parse it as your tool's data.
|
|
47449
|
+
|
|
47450
|
+
## Push vs re-execute (key for debugging)
|
|
47451
|
+
The host pushes \`tool-result\` from the tool result **recorded when the agent ran** \u2014
|
|
47452
|
+
it does **not** re-execute the handler. The push therefore always reflects that
|
|
47453
|
+
run's output (and the handler's \`result\` at that time). A \`tools/call\` from the
|
|
47454
|
+
View, by contrast, **re-executes** the handler live every time.
|
|
47455
|
+
|
|
47456
|
+
So if the push looks stale/wrong but a fresh \`tools/call\` returns the right data,
|
|
47457
|
+
the recorded run predates your change \u2014 re-run the tool (a new agent call) to get
|
|
47458
|
+
a fresh push. Do not assume the push re-reads the handler.
|
|
47459
|
+
|
|
47460
|
+
## Self-check / debug
|
|
47461
|
+
- Log the raw frame: \`console.log(JSON.stringify(ev.data))\` in the \`message\`
|
|
47462
|
+
listener, and open the browser console inside the sandboxed frame.
|
|
47463
|
+
- If the pushed text looks like the raw handler envelope (e.g. it starts with
|
|
47464
|
+
\`{"ok":true,...\`), the handler omitted the \`result\` wrapper \u2014 the host falls
|
|
47465
|
+
back to the raw stdout instead of your payload. Fix the handler (see
|
|
47466
|
+
\`sandbox-plugins-handlers\`).
|
|
47467
|
+
- The push only happens after \`ui/initialize\` **and**
|
|
47468
|
+
\`ui/notifications/initialized\`; a listener registered later misses it.
|
|
46673
47469
|
|
|
46674
47470
|
\`templates/ui.html\` implements all of this in vanilla JS and renders both payloads
|
|
46675
47471
|
into a \`#data\` element.
|
|
@@ -46789,6 +47585,76 @@ Templates: see \`sandbox-plugins-manifest\` for the exact \`tools[]\` fields; th
|
|
|
46789
47585
|
\`templates/plugin.json\` already includes \`"expose": true\` (the tool is grantable via
|
|
46790
47586
|
an API key but not callable until a key grants it). Remove it for tools that should
|
|
46791
47587
|
not be externally grantable at all.
|
|
47588
|
+
`,
|
|
47589
|
+
resources: {}
|
|
47590
|
+
},
|
|
47591
|
+
"sandbox-plugins-consume-open": {
|
|
47592
|
+
version: "1.0.0",
|
|
47593
|
+
content: `---
|
|
47594
|
+
name: sandbox-plugins-consume-open
|
|
47595
|
+
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.
|
|
47596
|
+
---
|
|
47597
|
+
|
|
47598
|
+
# Calling the platform from a handler (Open MCP)
|
|
47599
|
+
|
|
47600
|
+
A plugin handler can call **the platform's own tools and agents** through the Open
|
|
47601
|
+
MCP endpoint. Reach for this when a tool should orchestrate existing capabilities
|
|
47602
|
+
(read the knowledge base, create a task, invoke an agent) instead of
|
|
47603
|
+
re-implementing them.
|
|
47604
|
+
|
|
47605
|
+
## What the handler gets
|
|
47606
|
+
When the gateway injects the credential, each handler process starts with:
|
|
47607
|
+
- \`API_GATEWAY_URL\` \u2014 the gateway base URL.
|
|
47608
|
+
- \`OPEN_API_TOKEN\` \u2014 a short-lived bearer **scoped to this tenant's plugin callers**.
|
|
47609
|
+
|
|
47610
|
+
If the deployment does not inject them (no \`PUBLIC_GATEWAY_URL\`), the variables are
|
|
47611
|
+
**absent** \u2014 detect that and degrade gracefully (report "platform tools
|
|
47612
|
+
unavailable") rather than crashing.
|
|
47613
|
+
|
|
47614
|
+
## Discover, then call
|
|
47615
|
+
Never hardcode the tool list: the callable set is **reflected per tenant** and
|
|
47616
|
+
changes as plugins are exposed. Call \`listTools()\` at runtime.
|
|
47617
|
+
\`\`\`js
|
|
47618
|
+
const { OpenMcpClient } = require("@axiom-lattice/open-mcp-client");
|
|
47619
|
+
// or require("/root/.agents/sdk/open-mcp-client/index.js") when provisioned there
|
|
47620
|
+
|
|
47621
|
+
async function main() {
|
|
47622
|
+
const input = process.env.PLUGIN_INPUT_FILE
|
|
47623
|
+
? JSON.parse(require("fs").readFileSync(process.env.PLUGIN_INPUT_FILE, "utf8"))
|
|
47624
|
+
: JSON.parse(process.env.PLUGIN_INPUT || "{}");
|
|
47625
|
+
|
|
47626
|
+
const client = OpenMcpClient.fromEnv(); // API_GATEWAY_URL + OPEN_API_TOKEN
|
|
47627
|
+
const tools = await client.listTools(); // exactly what this handler may call
|
|
47628
|
+
const kb = await client.callTool("kb_search", { collection: "docs", query: input.q });
|
|
47629
|
+
|
|
47630
|
+
process.stdout.write(JSON.stringify({ ok: true, result: kb }));
|
|
47631
|
+
}
|
|
47632
|
+
main().catch((err) => {
|
|
47633
|
+
process.stdout.write(JSON.stringify({ ok: false, error: String(err && err.message ? err.message : err) }));
|
|
47634
|
+
});
|
|
47635
|
+
\`\`\`
|
|
47636
|
+
|
|
47637
|
+
- Tool names are \`{domain}_{action}\` (e.g. \`kb_search\`, \`task_manage_task\`).
|
|
47638
|
+
- Args follow the tool's \`inputSchema\`; \`listTools()\` returns name/description.
|
|
47639
|
+
- Return the result through the normal handler envelope (\`{ ok: true, result }\`)
|
|
47640
|
+
so it becomes this tool's output \u2014 see \`sandbox-plugins-handlers\`.
|
|
47641
|
+
|
|
47642
|
+
## What the handler may call
|
|
47643
|
+
The token carries the conservative sandbox defaults (\`sandbox_*\`, \`kb_search\`)
|
|
47644
|
+
**plus the tenant's grantable Open catalog**: every exposed tenant/global plugin
|
|
47645
|
+
tool and the tenant's agents. A call outside that set fails (tool-not-found or
|
|
47646
|
+
\`GRANT_DENIED\`); retrying will not help.
|
|
47647
|
+
|
|
47648
|
+
## Client availability
|
|
47649
|
+
The zero-dependency \`@axiom-lattice/open-mcp-client\` must be present in the
|
|
47650
|
+
sandbox: preinstalled in the image, at \`/root/.agents/sdk/open-mcp-client\`, or
|
|
47651
|
+
installed in this run (\`npm install @axiom-lattice/open-mcp-client\`). The CLI is
|
|
47652
|
+
also available: \`npx @axiom-lattice/open-mcp-client tools list --json\`.
|
|
47653
|
+
|
|
47654
|
+
## Limits
|
|
47655
|
+
- Nested sandbox orchestration is depth-limited (5); past the cap no token is
|
|
47656
|
+
re-issued, so deep recursion fails fast.
|
|
47657
|
+
- Only call what \`listTools()\` returns, and handle a missing tool gracefully.
|
|
46792
47658
|
`,
|
|
46793
47659
|
resources: {}
|
|
46794
47660
|
},
|
|
@@ -46829,10 +47695,23 @@ for the single/first connection, or \`data.connections\` (entries of
|
|
|
46829
47695
|
\`{ key, config }\`) when the plugin selects multiple connections.
|
|
46830
47696
|
|
|
46831
47697
|
## Writing output
|
|
46832
|
-
|
|
47698
|
+
Every tool handler must print **one** JSON envelope to stdout, with your payload
|
|
47699
|
+
under \`result\`:
|
|
46833
47700
|
\`\`\`js
|
|
46834
|
-
process.stdout.write(JSON.stringify({ ok: true, result:
|
|
47701
|
+
process.stdout.write(JSON.stringify({ ok: true, result: <payload> }));
|
|
46835
47702
|
\`\`\`
|
|
47703
|
+
|
|
47704
|
+
> **The payload MUST go under \`result\`.** \`result\` may be any JSON value
|
|
47705
|
+
> (object, array, string, number).
|
|
47706
|
+
> \`\`\`js
|
|
47707
|
+
> // \u2705 correct \u2014 data under \`result\`
|
|
47708
|
+
> process.stdout.write(JSON.stringify({ ok: true, result: { tasks, count } }));
|
|
47709
|
+
>
|
|
47710
|
+
> // \u274C wrong \u2014 \`result\` missing; the host surfaces the raw stdout (this
|
|
47711
|
+
> // envelope), so callers and MCP App UIs never see \`tasks\`
|
|
47712
|
+
> process.stdout.write(JSON.stringify({ ok: true, tasks, count }));
|
|
47713
|
+
> \`\`\`
|
|
47714
|
+
|
|
46836
47715
|
Non-JSON stdout is treated as a plain-text result. To fail, either print
|
|
46837
47716
|
\`{ ok: false, error }\` or exit non-zero (stderr is not required; stdout is
|
|
46838
47717
|
captured).
|
|
@@ -46933,6 +47812,70 @@ Fall back to \`execute_code\`/\`shell\` to run \`node handlers/x.js\` manually w
|
|
|
46933
47812
|
\`PLUGIN_INPUT\` and a synthetic \`PLUGIN_CONNECTION_FILE\` to reproduce logic bugs.
|
|
46934
47813
|
A synthetic fixture only verifies plumbing \u2014 a real upstream call requires a real
|
|
46935
47814
|
\`connectionKey\` configured by a human.
|
|
47815
|
+
`,
|
|
47816
|
+
resources: {}
|
|
47817
|
+
},
|
|
47818
|
+
"sandbox-plugins-deploy": {
|
|
47819
|
+
version: "1.0.0",
|
|
47820
|
+
content: `---
|
|
47821
|
+
name: sandbox-plugins-deploy
|
|
47822
|
+
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.
|
|
47823
|
+
---
|
|
47824
|
+
|
|
47825
|
+
# Deploying a Plugin as a Resident Service
|
|
47826
|
+
|
|
47827
|
+
By default a plugin handler runs as a short-lived process on each tool call.
|
|
47828
|
+
Optionally, you can **deploy** the plugin as a **resident service**: one
|
|
47829
|
+
long-lived sandbox per plugin that serves every call. This is faster for
|
|
47830
|
+
frequent or heavy calls, but costs a running sandbox.
|
|
47831
|
+
|
|
47832
|
+
## Precondition: tests must pass first
|
|
47833
|
+
Deploy ONLY after the plugin is validated AND its tests pass:
|
|
47834
|
+
1. \`manage_plugin validate\` \u2014 no diagnostics.
|
|
47835
|
+
2. \`manage_plugin create\`/\`update\` \u2014 written and reloaded.
|
|
47836
|
+
3. \`manage_plugin test\` \u2014 every tool/connection test returns \`ok: true\`
|
|
47837
|
+
(a real \`connectionKey\` test for credentialed tools).
|
|
47838
|
+
Never deploy a plugin you have not tested: a resident service wraps the same
|
|
47839
|
+
handlers, so an untested handler fails identically \u2014 just in a service you now
|
|
47840
|
+
have to undeploy.
|
|
47841
|
+
|
|
47842
|
+
## Choose a mode
|
|
47843
|
+
- \`"onDemand"\` (default) \u2014 the service is paused when idle and resumed on the
|
|
47844
|
+
next call. Best for plugins used occasionally. Lower steady cost.
|
|
47845
|
+
- \`"alwaysOn"\` \u2014 the service stays resident and is restarted if it becomes
|
|
47846
|
+
unhealthy. Best for plugins on a hot path or with expensive startup.
|
|
47847
|
+
|
|
47848
|
+
## Deploy
|
|
47849
|
+
\`\`\`jsonc
|
|
47850
|
+
{ "action": "deploy", "type": "my-plugin", "mode": "alwaysOn" }
|
|
47851
|
+
\`\`\`
|
|
47852
|
+
Returns \`{ deployed, status, endpoint, mode }\`. \`status\` is \`"running"\` when
|
|
47853
|
+
the service answered its health check. On failure you get
|
|
47854
|
+
\`{ deployed: false, error }\` \u2014 report it, do not treat it as success.
|
|
47855
|
+
Deploying again is idempotent: it reuses the same service.
|
|
47856
|
+
|
|
47857
|
+
## Check status
|
|
47858
|
+
\`\`\`jsonc
|
|
47859
|
+
{ "action": "status", "type": "my-plugin" }
|
|
47860
|
+
\`\`\`
|
|
47861
|
+
Returns \`{ status, endpoint?, mode?, lastHealthAt?, lastError? }\`. \`"absent"\`
|
|
47862
|
+
means no deployment exists; \`"failed"\` (with \`lastError\`) means the service is
|
|
47863
|
+
down. Inspect this before reporting the plugin as serving.
|
|
47864
|
+
|
|
47865
|
+
## Undeploy
|
|
47866
|
+
\`\`\`jsonc
|
|
47867
|
+
{ "action": "undeploy", "type": "my-plugin" }
|
|
47868
|
+
\`\`\`
|
|
47869
|
+
Returns \`{ undeployed }\`. Undeploy removes the resident sandbox and its
|
|
47870
|
+
deployment record; the plugin files and manifest are untouched, so tool calls
|
|
47871
|
+
fall back to the short-process path. Use it to stop paying for a service you no
|
|
47872
|
+
longer need.
|
|
47873
|
+
|
|
47874
|
+
## When resident services are unavailable
|
|
47875
|
+
If the environment has no deployment service registered, deploy/undeploy/status
|
|
47876
|
+
return a diagnostic (\`{ deployed: false, error }\` etc.) instead of throwing.
|
|
47877
|
+
That is not a plugin defect: the plugin still works through the default
|
|
47878
|
+
short-process path, so report it as not-deployed rather than broken.
|
|
46936
47879
|
`,
|
|
46937
47880
|
resources: {}
|
|
46938
47881
|
}
|
|
@@ -46954,6 +47897,8 @@ Follow the authoring skill's "Decide first" steps to confirm the goal
|
|
|
46954
47897
|
(inputs/outputs). If the UI class applies, load "sandbox-plugins-ui" before
|
|
46955
47898
|
authoring it. If the tool must be callable by external MCP clients via an API key,
|
|
46956
47899
|
load "sandbox-plugins-expose" and follow it (\`expose: true\` + key grants).
|
|
47900
|
+
If a tool must call the platform's own tools/agents from its handler, load
|
|
47901
|
+
"sandbox-plugins-consume-open".
|
|
46957
47902
|
|
|
46958
47903
|
Plan with persistent Tasks: MANDATORY until you load "task-tracking" with the
|
|
46959
47904
|
\`skill\` tool, call neither \`manage_task\` nor any other task operation. Use
|
|
@@ -46969,10 +47914,197 @@ the connector and give you its key. Only after a real \`connectionKey\` test
|
|
|
46969
47914
|
passes may you call the tool verified; without it, report functionality as
|
|
46970
47915
|
unverified (missing credentials), not as passing. Use \`shell_exec\` (the tool
|
|
46971
47916
|
provided by the \`code_eval\` middleware) to run \`node handlers/x.js\` for manual
|
|
46972
|
-
checks when reproducing a failure
|
|
47917
|
+
checks when reproducing a failure.
|
|
47918
|
+
|
|
47919
|
+
After the plugin passes \`validate\` and its tests, deploy it as a resident
|
|
47920
|
+
service: load "sandbox-plugins-deploy" and follow it. Choose \`onDemand\`
|
|
47921
|
+
(default) or \`alwaysOn\` for hot paths, call \`manage_plugin deploy\` with the
|
|
47922
|
+
chosen \`mode\`, then confirm with \`manage_plugin status\` (\`running\`). Only claim
|
|
47923
|
+
the plugin is deployed and serving once \`status\` reports \`running\`; if the
|
|
47924
|
+
environment reports no deployment service, say so and treat deployment as
|
|
47925
|
+
unavailable \u2014 do NOT claim success. \`undeploy\` removes the service without
|
|
47926
|
+
touching the plugin files.`;
|
|
47927
|
+
|
|
47928
|
+
// src/middlewares/sandboxPluginsUi.ts
|
|
47929
|
+
var SANDBOX_PLUGINS_UI_RESOURCE = "ui://sandbox-plugins/app";
|
|
47930
|
+
var SANDBOX_PLUGINS_APP_HTML = `<!doctype html>
|
|
47931
|
+
<html lang="en">
|
|
47932
|
+
<head>
|
|
47933
|
+
<meta charset="utf-8" />
|
|
47934
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
47935
|
+
<title>Plugin Deployments</title>
|
|
47936
|
+
<style>
|
|
47937
|
+
:root { color-scheme: light dark; }
|
|
47938
|
+
* { box-sizing: border-box; }
|
|
47939
|
+
body {
|
|
47940
|
+
margin: 0;
|
|
47941
|
+
padding: 14px 16px;
|
|
47942
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
47943
|
+
font-size: 13px;
|
|
47944
|
+
color: #1f2430;
|
|
47945
|
+
background: transparent;
|
|
47946
|
+
}
|
|
47947
|
+
h1 { font-size: 14px; font-weight: 600; margin: 0 0 2px; }
|
|
47948
|
+
.subtitle { color: #6b7280; font-size: 12px; margin: 0 0 12px; }
|
|
47949
|
+
table { width: 100%; border-collapse: collapse; }
|
|
47950
|
+
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid #eceef2; vertical-align: top; }
|
|
47951
|
+
th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: #6b7280; font-weight: 600; }
|
|
47952
|
+
td.type { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-weight: 600; white-space: nowrap; }
|
|
47953
|
+
td.mode { white-space: nowrap; color: #4b5563; }
|
|
47954
|
+
td.detail { color: #4b5563; word-break: break-word; max-width: 280px; }
|
|
47955
|
+
.muted { color: #9ca3af; }
|
|
47956
|
+
.status { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
|
|
47957
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; background: #9ca3af; }
|
|
47958
|
+
.dot.running { background: #22c55e; }
|
|
47959
|
+
.dot.failed { background: #ef4444; }
|
|
47960
|
+
.dot.requested, .dot.provisioning, .dot.stopping { background: #f59e0b; }
|
|
47961
|
+
.dot.paused, .dot.stopped { background: #94a3b8; }
|
|
47962
|
+
.state { color: #6b7280; padding: 14px 2px; }
|
|
47963
|
+
.error { margin: 0 0 10px; padding: 8px 10px; border-radius: 8px; background: #fef2f2; color: #b91c1c; }
|
|
47964
|
+
</style>
|
|
47965
|
+
</head>
|
|
47966
|
+
<body>
|
|
47967
|
+
<h1>Plugin Deployments</h1>
|
|
47968
|
+
<p class="subtitle">Resident services deployed for this tenant. Read-only.</p>
|
|
47969
|
+
<div id="root"><p class="state">Loading\u2026</p></div>
|
|
47970
|
+
<script>
|
|
47971
|
+
(function () {
|
|
47972
|
+
var root = document.getElementById("root");
|
|
47973
|
+
|
|
47974
|
+
function post(message) {
|
|
47975
|
+
window.parent.postMessage(message, "*");
|
|
47976
|
+
}
|
|
47977
|
+
function notify(method, params) {
|
|
47978
|
+
post({ jsonrpc: "2.0", method: method, params: params });
|
|
47979
|
+
}
|
|
47980
|
+
var nextId = 1;
|
|
47981
|
+
function rpc(method, params) {
|
|
47982
|
+
var id = nextId++;
|
|
47983
|
+
return new Promise(function (resolve, reject) {
|
|
47984
|
+
function onMessage(event) {
|
|
47985
|
+
var data = event.data;
|
|
47986
|
+
if (!data || data.id !== id) return;
|
|
47987
|
+
window.removeEventListener("message", onMessage);
|
|
47988
|
+
if (data.error) reject(new Error((data.error && data.error.message) || "rpc error"));
|
|
47989
|
+
else resolve(data.result);
|
|
47990
|
+
}
|
|
47991
|
+
window.addEventListener("message", onMessage);
|
|
47992
|
+
post({ jsonrpc: "2.0", id: id, method: method, params: params });
|
|
47993
|
+
});
|
|
47994
|
+
}
|
|
47995
|
+
|
|
47996
|
+
function sendSize() {
|
|
47997
|
+
var width = Math.ceil(document.documentElement.scrollWidth || document.body.scrollWidth);
|
|
47998
|
+
var height = Math.ceil(document.documentElement.scrollHeight || document.body.scrollHeight);
|
|
47999
|
+
notify("ui/notifications/size-changed", { width: width, height: height });
|
|
48000
|
+
}
|
|
48001
|
+
|
|
48002
|
+
function esc(value) {
|
|
48003
|
+
return String(value === null || value === undefined ? "" : value).replace(/[&<>"']/g, function (ch) {
|
|
48004
|
+
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch];
|
|
48005
|
+
});
|
|
48006
|
+
}
|
|
48007
|
+
|
|
48008
|
+
function statusClass(status) {
|
|
48009
|
+
switch (status) {
|
|
48010
|
+
case "running": return "running";
|
|
48011
|
+
case "failed": return "failed";
|
|
48012
|
+
case "requested":
|
|
48013
|
+
case "provisioning":
|
|
48014
|
+
case "stopping": return "requested";
|
|
48015
|
+
case "paused":
|
|
48016
|
+
case "stopped": return "paused";
|
|
48017
|
+
default: return "";
|
|
48018
|
+
}
|
|
48019
|
+
}
|
|
48020
|
+
|
|
48021
|
+
function healthCell(item) {
|
|
48022
|
+
if (item.lastError) return '<span style="color:#b91c1c">' + esc(item.lastError) + "</span>";
|
|
48023
|
+
if (item.lastHealthAt) return '<span class="muted">' + esc(item.lastHealthAt) + "</span>";
|
|
48024
|
+
return '<span class="muted">\u2014</span>';
|
|
48025
|
+
}
|
|
48026
|
+
|
|
48027
|
+
function render(payload) {
|
|
48028
|
+
var deployments = payload && Array.isArray(payload.deployments) ? payload.deployments : [];
|
|
48029
|
+
var error = payload && payload.error ? String(payload.error) : "";
|
|
48030
|
+
var html = "";
|
|
48031
|
+
if (error) html += '<div class="error">' + esc(error) + "</div>";
|
|
48032
|
+
if (deployments.length === 0) {
|
|
48033
|
+
html += '<p class="state">' + (error ? "No deployments available." : "No resident plugin deployments.") + "</p>";
|
|
48034
|
+
root.innerHTML = html;
|
|
48035
|
+
sendSize();
|
|
48036
|
+
return;
|
|
48037
|
+
}
|
|
48038
|
+
html += "<table><thead><tr><th>Plugin</th><th>Status</th><th>Mode</th><th>Endpoint</th><th>Health</th></tr></thead><tbody>";
|
|
48039
|
+
deployments.forEach(function (item) {
|
|
48040
|
+
var status = item && item.status ? String(item.status) : "unknown";
|
|
48041
|
+
html += "<tr>";
|
|
48042
|
+
html += '<td class="type">' + esc(item && item.pluginType) + "</td>";
|
|
48043
|
+
html += '<td><span class="status"><span class="dot ' + statusClass(status) + '"></span>' + esc(status) + "</span></td>";
|
|
48044
|
+
html += '<td class="mode">' + esc(item && item.mode) + "</td>";
|
|
48045
|
+
html += '<td class="detail">' + (item && item.endpoint ? esc(item.endpoint) : '<span class="muted">\u2014</span>') + "</td>";
|
|
48046
|
+
html += '<td class="detail">' + healthCell(item || {}) + "</td>";
|
|
48047
|
+
html += "</tr>";
|
|
48048
|
+
});
|
|
48049
|
+
html += "</tbody></table>";
|
|
48050
|
+
root.innerHTML = html;
|
|
48051
|
+
sendSize();
|
|
48052
|
+
}
|
|
48053
|
+
|
|
48054
|
+
function readResult(params) {
|
|
48055
|
+
if (!params || typeof params !== "object") return null;
|
|
48056
|
+
if (params.structuredContent && typeof params.structuredContent === "object") {
|
|
48057
|
+
return params.structuredContent;
|
|
48058
|
+
}
|
|
48059
|
+
var content = params.content;
|
|
48060
|
+
var text = content && content[0] && typeof content[0].text === "string" ? content[0].text : "";
|
|
48061
|
+
if (!text) return null;
|
|
48062
|
+
try { return JSON.parse(text); } catch (err) { return null; }
|
|
48063
|
+
}
|
|
48064
|
+
|
|
48065
|
+
// 1) Register the tool-result listener BEFORE initializing so the data
|
|
48066
|
+
// the host pushes right after the handshake is never missed.
|
|
48067
|
+
window.addEventListener("message", function (event) {
|
|
48068
|
+
var data = event.data;
|
|
48069
|
+
if (!data || typeof data !== "object" || typeof data.method !== "string") return;
|
|
48070
|
+
if (data.method === "ui/notifications/tool-result") {
|
|
48071
|
+
render(readResult(data.params));
|
|
48072
|
+
}
|
|
48073
|
+
});
|
|
48074
|
+
|
|
48075
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
48076
|
+
new ResizeObserver(sendSize).observe(document.documentElement);
|
|
48077
|
+
} else {
|
|
48078
|
+
window.addEventListener("resize", sendSize);
|
|
48079
|
+
}
|
|
48080
|
+
|
|
48081
|
+
// 2) MCP Apps lifecycle: initialize, then signal readiness. The host
|
|
48082
|
+
// pushes tool data only after this completes.
|
|
48083
|
+
rpc("ui/initialize", {
|
|
48084
|
+
protocolVersion: "2025-06-18",
|
|
48085
|
+
capabilities: {},
|
|
48086
|
+
clientInfo: { name: "sandbox-plugins-ui", version: "1.0.0" },
|
|
48087
|
+
appCapabilities: { availableDisplayModes: ["inline"] }
|
|
48088
|
+
}).then(function () {
|
|
48089
|
+
notify("ui/notifications/initialized", {});
|
|
48090
|
+
sendSize();
|
|
48091
|
+
}).catch(function (err) {
|
|
48092
|
+
root.innerHTML = '<p class="state">Failed to initialize: ' + esc((err && err.message) || err) + "</p>";
|
|
48093
|
+
sendSize();
|
|
48094
|
+
});
|
|
48095
|
+
})();
|
|
48096
|
+
</script>
|
|
48097
|
+
</body>
|
|
48098
|
+
</html>
|
|
48099
|
+
`;
|
|
46973
48100
|
|
|
46974
48101
|
// src/middlewares/sandboxPluginsMiddleware.ts
|
|
46975
|
-
var
|
|
48102
|
+
var SANDBOX_PLUGINS_UI_REF = {
|
|
48103
|
+
kind: "plugin",
|
|
48104
|
+
pluginType: "sandbox-plugins",
|
|
48105
|
+
resource: SANDBOX_PLUGINS_UI_RESOURCE,
|
|
48106
|
+
displayMode: "inline"
|
|
48107
|
+
};
|
|
46976
48108
|
function tenantIdFrom(exeConfig) {
|
|
46977
48109
|
const c = exeConfig;
|
|
46978
48110
|
return c?.configurable?.runConfig?.tenantId ?? "default";
|
|
@@ -46989,6 +48121,15 @@ function collectSecretValues(connection, fields) {
|
|
|
46989
48121
|
function errorText(err) {
|
|
46990
48122
|
return err instanceof Error ? err.message : String(err);
|
|
46991
48123
|
}
|
|
48124
|
+
function deploymentUnavailable(action) {
|
|
48125
|
+
const error = `plugin deployment service is not configured; cannot ${action}. Resident plugin services are unavailable in this environment.`;
|
|
48126
|
+
if (action === "undeploy") return JSON.stringify({ undeployed: false, error });
|
|
48127
|
+
if (action === "status") return JSON.stringify({ status: "unavailable", error });
|
|
48128
|
+
return JSON.stringify({ deployed: false, error });
|
|
48129
|
+
}
|
|
48130
|
+
function deploymentsResult(payload) {
|
|
48131
|
+
return appendUiFence(JSON.stringify(payload), SANDBOX_PLUGINS_UI_REF);
|
|
48132
|
+
}
|
|
46992
48133
|
function redactString(value, secrets) {
|
|
46993
48134
|
let out = value;
|
|
46994
48135
|
for (const secret of secrets) out = out.split(secret).join("[redacted]");
|
|
@@ -47009,10 +48150,24 @@ function redactSecretValues(payload, secrets) {
|
|
|
47009
48150
|
return walk(payload);
|
|
47010
48151
|
}
|
|
47011
48152
|
var managePluginSchema = z73.object({
|
|
47012
|
-
action: z73.enum([
|
|
48153
|
+
action: z73.enum([
|
|
48154
|
+
"list",
|
|
48155
|
+
"get",
|
|
48156
|
+
"validate",
|
|
48157
|
+
"create",
|
|
48158
|
+
"update",
|
|
48159
|
+
"delete",
|
|
48160
|
+
"reload",
|
|
48161
|
+
"test",
|
|
48162
|
+
"deploy",
|
|
48163
|
+
"undeploy",
|
|
48164
|
+
"status",
|
|
48165
|
+
"deployments"
|
|
48166
|
+
]),
|
|
47013
48167
|
type: z73.string().optional(),
|
|
47014
48168
|
manifest: z73.record(z73.unknown()).optional(),
|
|
47015
48169
|
handlers: z73.record(z73.string()).optional(),
|
|
48170
|
+
mode: z73.enum(["onDemand", "alwaysOn"]).optional(),
|
|
47016
48171
|
target: z73.object({
|
|
47017
48172
|
kind: z73.enum(["tool", "connection-test", "connection-discover"]),
|
|
47018
48173
|
name: z73.string().optional(),
|
|
@@ -47025,9 +48180,18 @@ var sandboxPluginsPlugin = {
|
|
|
47025
48180
|
type: "sandbox-plugins",
|
|
47026
48181
|
category: "execution",
|
|
47027
48182
|
name: "Sandbox Plugins",
|
|
47028
|
-
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.",
|
|
48183
|
+
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.",
|
|
47029
48184
|
recommends: ["skill"],
|
|
47030
|
-
tools: [
|
|
48185
|
+
tools: [
|
|
48186
|
+
{
|
|
48187
|
+
name: "manage_plugin",
|
|
48188
|
+
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.",
|
|
48189
|
+
ui: { resource: SANDBOX_PLUGINS_UI_RESOURCE, displayMode: "inline" }
|
|
48190
|
+
}
|
|
48191
|
+
],
|
|
48192
|
+
uiResources: {
|
|
48193
|
+
[SANDBOX_PLUGINS_UI_RESOURCE]: { html: SANDBOX_PLUGINS_APP_HTML }
|
|
48194
|
+
},
|
|
47031
48195
|
configSchema: { type: "object", properties: {} },
|
|
47032
48196
|
defaultConfig: {}
|
|
47033
48197
|
},
|
|
@@ -47048,7 +48212,7 @@ var sandboxPluginsPlugin = {
|
|
|
47048
48212
|
id: "sandbox-plugins",
|
|
47049
48213
|
type: "sandbox-plugins",
|
|
47050
48214
|
name: "Sandbox Plugins",
|
|
47051
|
-
description: "Author and manage tenant plugins (list/validate/create/update/delete/reload/test)",
|
|
48215
|
+
description: "Author and manage tenant plugins (list/validate/create/update/delete/reload/test/deploy/undeploy/status)",
|
|
47052
48216
|
enabled: true,
|
|
47053
48217
|
config: {}
|
|
47054
48218
|
},
|
|
@@ -47056,9 +48220,9 @@ var sandboxPluginsPlugin = {
|
|
|
47056
48220
|
id: "skill",
|
|
47057
48221
|
type: "skill",
|
|
47058
48222
|
name: "Skill",
|
|
47059
|
-
description: "Load the sandbox-plugins authoring, patterns, manifest, handler, connection, UI, and testing skills",
|
|
48223
|
+
description: "Load any available skill, including the sandbox-plugins authoring, patterns, manifest, handler, connection, UI, and testing skills",
|
|
47060
48224
|
enabled: true,
|
|
47061
|
-
config: { readAll:
|
|
48225
|
+
config: { readAll: true }
|
|
47062
48226
|
},
|
|
47063
48227
|
{
|
|
47064
48228
|
id: "task",
|
|
@@ -47239,18 +48403,79 @@ var sandboxPluginsPlugin = {
|
|
|
47239
48403
|
assistantId: typeof runCfg.assistant_id === "string" ? runCfg.assistant_id : void 0
|
|
47240
48404
|
}
|
|
47241
48405
|
});
|
|
47242
|
-
return
|
|
48406
|
+
return handlerResultText(redactSecretValues(result, secrets));
|
|
47243
48407
|
} catch (err) {
|
|
47244
48408
|
return JSON.stringify(redactSecretValues({ ok: false, error: errorText(err) }, secrets));
|
|
47245
48409
|
}
|
|
47246
48410
|
}
|
|
48411
|
+
case "deploy": {
|
|
48412
|
+
if (!input.type) return "type is required";
|
|
48413
|
+
const service3 = getPluginDeploymentService();
|
|
48414
|
+
if (!service3) return deploymentUnavailable("deploy");
|
|
48415
|
+
try {
|
|
48416
|
+
const result = await service3.deploy(tenantId2, input.type, {
|
|
48417
|
+
...input.mode === void 0 ? {} : { mode: input.mode }
|
|
48418
|
+
});
|
|
48419
|
+
return JSON.stringify({
|
|
48420
|
+
deployed: true,
|
|
48421
|
+
status: result.status,
|
|
48422
|
+
endpoint: result.endpoint,
|
|
48423
|
+
mode: result.mode
|
|
48424
|
+
});
|
|
48425
|
+
} catch (err) {
|
|
48426
|
+
return JSON.stringify({ deployed: false, error: errorText(err) });
|
|
48427
|
+
}
|
|
48428
|
+
}
|
|
48429
|
+
case "undeploy": {
|
|
48430
|
+
if (!input.type) return "type is required";
|
|
48431
|
+
const service3 = getPluginDeploymentService();
|
|
48432
|
+
if (!service3) return deploymentUnavailable("undeploy");
|
|
48433
|
+
try {
|
|
48434
|
+
const result = await service3.undeploy(tenantId2, input.type);
|
|
48435
|
+
return JSON.stringify({ undeployed: result.undeployed });
|
|
48436
|
+
} catch (err) {
|
|
48437
|
+
return JSON.stringify({ undeployed: false, error: errorText(err) });
|
|
48438
|
+
}
|
|
48439
|
+
}
|
|
48440
|
+
case "status": {
|
|
48441
|
+
if (!input.type) return "type is required";
|
|
48442
|
+
const service3 = getPluginDeploymentService();
|
|
48443
|
+
if (!service3) return deploymentUnavailable("status");
|
|
48444
|
+
try {
|
|
48445
|
+
const result = await service3.status(tenantId2, input.type);
|
|
48446
|
+
return JSON.stringify({
|
|
48447
|
+
status: result.status,
|
|
48448
|
+
endpoint: result.endpoint,
|
|
48449
|
+
mode: result.mode,
|
|
48450
|
+
lastHealthAt: result.lastHealthAt,
|
|
48451
|
+
lastError: result.lastError
|
|
48452
|
+
});
|
|
48453
|
+
} catch (err) {
|
|
48454
|
+
return JSON.stringify({ status: "unavailable", error: errorText(err) });
|
|
48455
|
+
}
|
|
48456
|
+
}
|
|
48457
|
+
case "deployments": {
|
|
48458
|
+
const service3 = getPluginDeploymentService();
|
|
48459
|
+
if (!service3) {
|
|
48460
|
+
return deploymentsResult({
|
|
48461
|
+
deployments: [],
|
|
48462
|
+
error: "plugin deployment service is not configured; cannot list deployments. Resident plugin services are unavailable in this environment."
|
|
48463
|
+
});
|
|
48464
|
+
}
|
|
48465
|
+
try {
|
|
48466
|
+
const deployments = await service3.list(tenantId2);
|
|
48467
|
+
return deploymentsResult({ deployments });
|
|
48468
|
+
} catch (err) {
|
|
48469
|
+
return deploymentsResult({ deployments: [], error: errorText(err) });
|
|
48470
|
+
}
|
|
48471
|
+
}
|
|
47247
48472
|
default:
|
|
47248
48473
|
return `unsupported action: ${input.action}`;
|
|
47249
48474
|
}
|
|
47250
48475
|
},
|
|
47251
48476
|
{
|
|
47252
48477
|
name: "manage_plugin",
|
|
47253
|
-
description: "Author and manage tenant sandbox plugins (list/get/validate/create/update/delete/reload/test).",
|
|
48478
|
+
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.",
|
|
47254
48479
|
schema: managePluginSchema
|
|
47255
48480
|
}
|
|
47256
48481
|
)
|
|
@@ -47305,6 +48530,526 @@ function buildBundleFromDist(distDir, version) {
|
|
|
47305
48530
|
};
|
|
47306
48531
|
}
|
|
47307
48532
|
|
|
48533
|
+
// src/sandbox_plugin/PluginDeploymentRuntimeRegistry.ts
|
|
48534
|
+
var PluginDeploymentRuntimeRegistry = class {
|
|
48535
|
+
/**
|
|
48536
|
+
* @param options - Optional injectable clock for deterministic health timestamps.
|
|
48537
|
+
*/
|
|
48538
|
+
constructor(options = {}) {
|
|
48539
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
48540
|
+
this.inFlight = /* @__PURE__ */ new Map();
|
|
48541
|
+
this.now = options.now ?? Date.now;
|
|
48542
|
+
}
|
|
48543
|
+
/**
|
|
48544
|
+
* Cache the resolved proxy endpoint for a deployment.
|
|
48545
|
+
*
|
|
48546
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
48547
|
+
* @param endpoint - Relative proxy path, e.g. `/api/sandboxes/plugin-abc/proxy/8080`.
|
|
48548
|
+
*/
|
|
48549
|
+
setEndpoint(id, endpoint) {
|
|
48550
|
+
this.entry(id).endpoint = endpoint;
|
|
48551
|
+
}
|
|
48552
|
+
/**
|
|
48553
|
+
* Read the cached endpoint for a deployment.
|
|
48554
|
+
*
|
|
48555
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
48556
|
+
* @returns The cached endpoint, or `undefined` when none is known.
|
|
48557
|
+
*/
|
|
48558
|
+
getEndpoint(id) {
|
|
48559
|
+
return this.entries.get(id)?.endpoint;
|
|
48560
|
+
}
|
|
48561
|
+
/**
|
|
48562
|
+
* Record a successful health observation and clear any prior error.
|
|
48563
|
+
*
|
|
48564
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
48565
|
+
*/
|
|
48566
|
+
markHealthy(id) {
|
|
48567
|
+
const entry = this.entry(id);
|
|
48568
|
+
entry.lastHealthAt = this.now();
|
|
48569
|
+
delete entry.lastError;
|
|
48570
|
+
}
|
|
48571
|
+
/**
|
|
48572
|
+
* Record a failed health observation.
|
|
48573
|
+
*
|
|
48574
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
48575
|
+
* @param error - Human-readable failure description.
|
|
48576
|
+
*/
|
|
48577
|
+
markUnhealthy(id, error) {
|
|
48578
|
+
const entry = this.entry(id);
|
|
48579
|
+
entry.lastHealthAt = this.now();
|
|
48580
|
+
entry.lastError = error;
|
|
48581
|
+
}
|
|
48582
|
+
/**
|
|
48583
|
+
* Read the last observed health for a deployment.
|
|
48584
|
+
*
|
|
48585
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
48586
|
+
* @returns The cached health, or `undefined` when the id is unknown.
|
|
48587
|
+
*/
|
|
48588
|
+
getHealth(id) {
|
|
48589
|
+
const entry = this.entries.get(id);
|
|
48590
|
+
if (!entry) return void 0;
|
|
48591
|
+
const health = {};
|
|
48592
|
+
if (entry.lastHealthAt !== void 0) health.lastHealthAt = entry.lastHealthAt;
|
|
48593
|
+
if (entry.lastError !== void 0) health.lastError = entry.lastError;
|
|
48594
|
+
return health;
|
|
48595
|
+
}
|
|
48596
|
+
/**
|
|
48597
|
+
* Run a cold start/resume factory at most once per id at a time.
|
|
48598
|
+
*
|
|
48599
|
+
* Concurrent calls for the same id return the same in-flight promise and run
|
|
48600
|
+
* `factory` exactly once. The in-flight entry is cleared when the factory
|
|
48601
|
+
* settles, so a later call runs it again (retry after failure is allowed).
|
|
48602
|
+
*
|
|
48603
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
48604
|
+
* @param factory - Idempotent start/resume operation for the resident service.
|
|
48605
|
+
* @returns A promise that settles with the factory outcome.
|
|
48606
|
+
*/
|
|
48607
|
+
ensureRunning(id, factory) {
|
|
48608
|
+
const existing = this.inFlight.get(id);
|
|
48609
|
+
if (existing) return existing;
|
|
48610
|
+
let started;
|
|
48611
|
+
try {
|
|
48612
|
+
started = factory();
|
|
48613
|
+
} catch (error) {
|
|
48614
|
+
started = Promise.reject(error);
|
|
48615
|
+
}
|
|
48616
|
+
const tracked = started.finally(() => {
|
|
48617
|
+
if (this.inFlight.get(id) === tracked) {
|
|
48618
|
+
this.inFlight.delete(id);
|
|
48619
|
+
}
|
|
48620
|
+
});
|
|
48621
|
+
this.inFlight.set(id, tracked);
|
|
48622
|
+
return tracked;
|
|
48623
|
+
}
|
|
48624
|
+
/**
|
|
48625
|
+
* Drop cached endpoint and health for one deployment.
|
|
48626
|
+
*
|
|
48627
|
+
* @param id - Composed key `${tenantId}:${pluginType}`.
|
|
48628
|
+
*/
|
|
48629
|
+
clear(id) {
|
|
48630
|
+
this.entries.delete(id);
|
|
48631
|
+
}
|
|
48632
|
+
/** Drop all cached endpoints, health, and in-flight coordination entries. */
|
|
48633
|
+
reset() {
|
|
48634
|
+
this.entries.clear();
|
|
48635
|
+
this.inFlight.clear();
|
|
48636
|
+
}
|
|
48637
|
+
entry(id) {
|
|
48638
|
+
const existing = this.entries.get(id);
|
|
48639
|
+
if (existing) return existing;
|
|
48640
|
+
const created = {};
|
|
48641
|
+
this.entries.set(id, created);
|
|
48642
|
+
return created;
|
|
48643
|
+
}
|
|
48644
|
+
};
|
|
48645
|
+
|
|
48646
|
+
// src/sandbox_plugin/host/pluginHostProtocol.ts
|
|
48647
|
+
var INLINE_INPUT_LIMIT_BYTES2 = 48 * 1024;
|
|
48648
|
+
var DEFAULT_HANDLER_TIMEOUT_MS = 6e4;
|
|
48649
|
+
var MIN_HANDLER_TIMEOUT_MS = 1e3;
|
|
48650
|
+
var MAX_HANDLER_TIMEOUT_MS = 3e5;
|
|
48651
|
+
function isRecord11(value) {
|
|
48652
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
48653
|
+
}
|
|
48654
|
+
function isEnvelope3(value) {
|
|
48655
|
+
return isRecord11(value) && typeof value.ok === "boolean";
|
|
48656
|
+
}
|
|
48657
|
+
function parseHostArgs(argv) {
|
|
48658
|
+
let type;
|
|
48659
|
+
let port;
|
|
48660
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
48661
|
+
const token = argv[index];
|
|
48662
|
+
if (token === "--type" && index + 1 < argv.length) {
|
|
48663
|
+
type = argv[index + 1];
|
|
48664
|
+
index += 1;
|
|
48665
|
+
} else if (token.startsWith("--type=")) {
|
|
48666
|
+
type = token.slice("--type=".length);
|
|
48667
|
+
} else if (token === "--port" && index + 1 < argv.length) {
|
|
48668
|
+
port = argv[index + 1];
|
|
48669
|
+
index += 1;
|
|
48670
|
+
} else if (token.startsWith("--port=")) {
|
|
48671
|
+
port = token.slice("--port=".length);
|
|
48672
|
+
}
|
|
48673
|
+
}
|
|
48674
|
+
const parsedPort = Number(port);
|
|
48675
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
48676
|
+
throw new Error("parseHostArgs: missing or empty --type");
|
|
48677
|
+
}
|
|
48678
|
+
if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
|
|
48679
|
+
throw new Error("parseHostArgs: invalid --port");
|
|
48680
|
+
}
|
|
48681
|
+
return { type, port: parsedPort };
|
|
48682
|
+
}
|
|
48683
|
+
function resolveHandlerTimeoutMs(value) {
|
|
48684
|
+
if (value === null || value === void 0 || value === "") return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
48685
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
48686
|
+
if (!Number.isFinite(numeric)) return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
48687
|
+
return Math.min(Math.max(Math.round(numeric), MIN_HANDLER_TIMEOUT_MS), MAX_HANDLER_TIMEOUT_MS);
|
|
48688
|
+
}
|
|
48689
|
+
function parseHandlerEnvelope(stdout) {
|
|
48690
|
+
const trimmed = stdout.trim();
|
|
48691
|
+
if (!trimmed) return { ok: false, error: "empty handler output" };
|
|
48692
|
+
const whole = tryParse(trimmed);
|
|
48693
|
+
if (isEnvelope3(whole)) return normalizeEnvelope(whole);
|
|
48694
|
+
const lines = stdout.split("\n");
|
|
48695
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
48696
|
+
const line = lines[index]?.trim();
|
|
48697
|
+
if (!line) continue;
|
|
48698
|
+
const candidate = tryParse(line);
|
|
48699
|
+
if (isEnvelope3(candidate)) return normalizeEnvelope(candidate);
|
|
48700
|
+
}
|
|
48701
|
+
return { ok: true, result: trimmed };
|
|
48702
|
+
}
|
|
48703
|
+
function buildHandlerCommand(handlerPath) {
|
|
48704
|
+
return `node ${quotePosixShellArg2(handlerPath)}`;
|
|
48705
|
+
}
|
|
48706
|
+
function encodeInput(input) {
|
|
48707
|
+
return Buffer.from(JSON.stringify(input ?? null), "utf8").toString("base64");
|
|
48708
|
+
}
|
|
48709
|
+
function buildConnectionPayload2(args) {
|
|
48710
|
+
if (Array.isArray(args.connections) && args.connections.length > 0) {
|
|
48711
|
+
return {
|
|
48712
|
+
connection: args.connection ?? args.connections[0]?.config,
|
|
48713
|
+
connections: args.connections
|
|
48714
|
+
};
|
|
48715
|
+
}
|
|
48716
|
+
if (args.connection) {
|
|
48717
|
+
return { connection: args.connection };
|
|
48718
|
+
}
|
|
48719
|
+
return null;
|
|
48720
|
+
}
|
|
48721
|
+
function quotePosixShellArg2(value) {
|
|
48722
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
48723
|
+
}
|
|
48724
|
+
function tryParse(text) {
|
|
48725
|
+
try {
|
|
48726
|
+
return JSON.parse(text);
|
|
48727
|
+
} catch {
|
|
48728
|
+
return void 0;
|
|
48729
|
+
}
|
|
48730
|
+
}
|
|
48731
|
+
function normalizeEnvelope(parsed) {
|
|
48732
|
+
if (parsed.ok) return { ok: true, result: parsed.result };
|
|
48733
|
+
return { ok: false, error: typeof parsed.error === "string" ? parsed.error : void 0 };
|
|
48734
|
+
}
|
|
48735
|
+
|
|
48736
|
+
// src/sandbox_plugin/host/pluginHostSource.ts
|
|
48737
|
+
import { createHash as createHash8 } from "crypto";
|
|
48738
|
+
var PLUGIN_HOST_VERSION = "1.1.0";
|
|
48739
|
+
var PLUGIN_HOST_SERVER_SOURCE = String.raw`#!/usr/bin/env node
|
|
48740
|
+
"use strict";
|
|
48741
|
+
|
|
48742
|
+
var http = require("node:http");
|
|
48743
|
+
var fs = require("node:fs");
|
|
48744
|
+
var os = require("node:os");
|
|
48745
|
+
var path = require("node:path");
|
|
48746
|
+
var crypto = require("node:crypto");
|
|
48747
|
+
var execFile = require("node:child_process").execFile;
|
|
48748
|
+
|
|
48749
|
+
var VERSION = "${PLUGIN_HOST_VERSION}";
|
|
48750
|
+
var PLUGINS_ROOT = "/root/.agents/plugins";
|
|
48751
|
+
var INLINE_INPUT_LIMIT_BYTES = 48 * 1024;
|
|
48752
|
+
var MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
48753
|
+
var MAX_RESULT_BYTES = 16 * 1024 * 1024;
|
|
48754
|
+
var DEFAULT_HANDLER_TIMEOUT_MS = 60000;
|
|
48755
|
+
var MIN_HANDLER_TIMEOUT_MS = 1000;
|
|
48756
|
+
var MAX_HANDLER_TIMEOUT_MS = 300000;
|
|
48757
|
+
|
|
48758
|
+
function resolveHandlerTimeoutMs(value) {
|
|
48759
|
+
if (value === null || value === undefined || value === "") {
|
|
48760
|
+
return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
48761
|
+
}
|
|
48762
|
+
var numeric = typeof value === "number" ? value : Number(value);
|
|
48763
|
+
if (!isFinite(numeric)) {
|
|
48764
|
+
return DEFAULT_HANDLER_TIMEOUT_MS;
|
|
48765
|
+
}
|
|
48766
|
+
return Math.min(Math.max(Math.round(numeric), MIN_HANDLER_TIMEOUT_MS), MAX_HANDLER_TIMEOUT_MS);
|
|
48767
|
+
}
|
|
48768
|
+
|
|
48769
|
+
function isRecord(value) {
|
|
48770
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
48771
|
+
}
|
|
48772
|
+
|
|
48773
|
+
function isEnvelope(value) {
|
|
48774
|
+
return isRecord(value) && typeof value.ok === "boolean";
|
|
48775
|
+
}
|
|
48776
|
+
|
|
48777
|
+
function tryParse(text) {
|
|
48778
|
+
try {
|
|
48779
|
+
return JSON.parse(text);
|
|
48780
|
+
} catch (err) {
|
|
48781
|
+
return undefined;
|
|
48782
|
+
}
|
|
48783
|
+
}
|
|
48784
|
+
|
|
48785
|
+
function normalizeEnvelope(parsed) {
|
|
48786
|
+
if (parsed.ok) {
|
|
48787
|
+
return { ok: true, result: parsed.result };
|
|
48788
|
+
}
|
|
48789
|
+
return { ok: false, error: typeof parsed.error === "string" ? parsed.error : undefined };
|
|
48790
|
+
}
|
|
48791
|
+
|
|
48792
|
+
function parseHandlerEnvelope(stdout) {
|
|
48793
|
+
var text = String(stdout == null ? "" : stdout);
|
|
48794
|
+
var trimmed = text.trim();
|
|
48795
|
+
if (!trimmed) {
|
|
48796
|
+
// Deliberate divergence from SandboxPluginExecutor, which returns
|
|
48797
|
+
// { ok: true, result: "" } for empty stdout; the resident host treats empty
|
|
48798
|
+
// output as a handler failure so a crashed/no-op handler is not a "success".
|
|
48799
|
+
return { ok: false, error: "empty handler output" };
|
|
48800
|
+
}
|
|
48801
|
+
var whole = tryParse(trimmed);
|
|
48802
|
+
if (isEnvelope(whole)) {
|
|
48803
|
+
return normalizeEnvelope(whole);
|
|
48804
|
+
}
|
|
48805
|
+
var lines = text.split("\n");
|
|
48806
|
+
for (var index = lines.length - 1; index >= 0; index -= 1) {
|
|
48807
|
+
var line = lines[index].trim();
|
|
48808
|
+
if (!line) {
|
|
48809
|
+
continue;
|
|
48810
|
+
}
|
|
48811
|
+
var candidate = tryParse(line);
|
|
48812
|
+
if (isEnvelope(candidate)) {
|
|
48813
|
+
return normalizeEnvelope(candidate);
|
|
48814
|
+
}
|
|
48815
|
+
}
|
|
48816
|
+
return { ok: true, result: trimmed };
|
|
48817
|
+
}
|
|
48818
|
+
|
|
48819
|
+
function quotePosixShellArg(value) {
|
|
48820
|
+
return "'" + String(value).replace(/'/g, "'\"'\"'") + "'";
|
|
48821
|
+
}
|
|
48822
|
+
|
|
48823
|
+
function buildHandlerCommand(handlerPath) {
|
|
48824
|
+
return "node " + quotePosixShellArg(handlerPath);
|
|
48825
|
+
}
|
|
48826
|
+
|
|
48827
|
+
function parseHostArgs(argv) {
|
|
48828
|
+
var type;
|
|
48829
|
+
var port;
|
|
48830
|
+
for (var index = 0; index < argv.length; index += 1) {
|
|
48831
|
+
var token = argv[index];
|
|
48832
|
+
if (token === "--type" && index + 1 < argv.length) {
|
|
48833
|
+
type = argv[index + 1];
|
|
48834
|
+
index += 1;
|
|
48835
|
+
} else if (token.indexOf("--type=") === 0) {
|
|
48836
|
+
type = token.slice("--type=".length);
|
|
48837
|
+
} else if (token === "--port" && index + 1 < argv.length) {
|
|
48838
|
+
port = argv[index + 1];
|
|
48839
|
+
index += 1;
|
|
48840
|
+
} else if (token.indexOf("--port=") === 0) {
|
|
48841
|
+
port = token.slice("--port=".length);
|
|
48842
|
+
}
|
|
48843
|
+
}
|
|
48844
|
+
var parsedPort = Number(port);
|
|
48845
|
+
if (typeof type !== "string" || type.length === 0) {
|
|
48846
|
+
throw new Error("parseHostArgs: missing or empty --type");
|
|
48847
|
+
}
|
|
48848
|
+
if (!Number.isInteger(parsedPort) || parsedPort <= 0 || parsedPort > 65535) {
|
|
48849
|
+
throw new Error("parseHostArgs: invalid --port");
|
|
48850
|
+
}
|
|
48851
|
+
return { type: type, port: parsedPort };
|
|
48852
|
+
}
|
|
48853
|
+
|
|
48854
|
+
function encodeInput(input) {
|
|
48855
|
+
return Buffer.from(JSON.stringify(input === undefined ? null : input), "utf8").toString("base64");
|
|
48856
|
+
}
|
|
48857
|
+
|
|
48858
|
+
function buildConnectionPayload(body) {
|
|
48859
|
+
if (Array.isArray(body.connections) && body.connections.length > 0) {
|
|
48860
|
+
return {
|
|
48861
|
+
connection: body.connection !== undefined ? body.connection : body.connections[0].config,
|
|
48862
|
+
connections: body.connections,
|
|
48863
|
+
};
|
|
48864
|
+
}
|
|
48865
|
+
if (body.connection) {
|
|
48866
|
+
return { connection: body.connection };
|
|
48867
|
+
}
|
|
48868
|
+
return null;
|
|
48869
|
+
}
|
|
48870
|
+
|
|
48871
|
+
function resolveHandlerPath(type, handler) {
|
|
48872
|
+
if (typeof handler !== "string" || handler.length === 0) {
|
|
48873
|
+
throw new Error("missing handler");
|
|
48874
|
+
}
|
|
48875
|
+
if (handler.charAt(0) === "/" || handler.split("/").indexOf("..") !== -1) {
|
|
48876
|
+
throw new Error("invalid handler path");
|
|
48877
|
+
}
|
|
48878
|
+
return path.join(PLUGINS_ROOT, type, handler);
|
|
48879
|
+
}
|
|
48880
|
+
|
|
48881
|
+
function readJsonBody(req) {
|
|
48882
|
+
return new Promise(function (resolve, reject) {
|
|
48883
|
+
var chunks = [];
|
|
48884
|
+
var size = 0;
|
|
48885
|
+
req.on("data", function (chunk) {
|
|
48886
|
+
size += chunk.length;
|
|
48887
|
+
if (size > MAX_BODY_BYTES) {
|
|
48888
|
+
reject(new Error("request body too large"));
|
|
48889
|
+
req.destroy();
|
|
48890
|
+
return;
|
|
48891
|
+
}
|
|
48892
|
+
chunks.push(chunk);
|
|
48893
|
+
});
|
|
48894
|
+
req.on("end", function () {
|
|
48895
|
+
try {
|
|
48896
|
+
var text = Buffer.concat(chunks).toString("utf8");
|
|
48897
|
+
resolve(text ? JSON.parse(text) : {});
|
|
48898
|
+
} catch (err) {
|
|
48899
|
+
reject(new Error("invalid JSON body"));
|
|
48900
|
+
}
|
|
48901
|
+
});
|
|
48902
|
+
req.on("error", reject);
|
|
48903
|
+
});
|
|
48904
|
+
}
|
|
48905
|
+
|
|
48906
|
+
function runNode(handlerPath, pluginDir, env, timeoutMs) {
|
|
48907
|
+
return new Promise(function (resolve) {
|
|
48908
|
+
execFile(
|
|
48909
|
+
process.execPath,
|
|
48910
|
+
[handlerPath],
|
|
48911
|
+
{ cwd: pluginDir, env: env, timeout: timeoutMs, maxBuffer: MAX_RESULT_BYTES },
|
|
48912
|
+
function (error, stdout, stderr) {
|
|
48913
|
+
resolve({ error: error, stdout: stdout || "", stderr: stderr || "" });
|
|
48914
|
+
}
|
|
48915
|
+
);
|
|
48916
|
+
});
|
|
48917
|
+
}
|
|
48918
|
+
|
|
48919
|
+
function failureFromExec(exec, command) {
|
|
48920
|
+
var parsed = parseHandlerEnvelope(exec.stdout);
|
|
48921
|
+
if (!parsed.ok) {
|
|
48922
|
+
return parsed;
|
|
48923
|
+
}
|
|
48924
|
+
var code = exec.error && typeof exec.error.code === "number" ? exec.error.code : "n/a";
|
|
48925
|
+
var detail = exec.stdout ? exec.stdout.slice(0, 4096) : exec.error ? String(exec.error.message) : "";
|
|
48926
|
+
return { ok: false, error: "handler failed (exit " + code + "): " + detail + " [" + command + "]" };
|
|
48927
|
+
}
|
|
48928
|
+
|
|
48929
|
+
async function runHandler(body) {
|
|
48930
|
+
var pluginDir = path.join(PLUGINS_ROOT, config.type);
|
|
48931
|
+
var handlerPath = resolveHandlerPath(config.type, body.handler);
|
|
48932
|
+
var command = buildHandlerCommand(handlerPath);
|
|
48933
|
+
var inputJson = JSON.stringify(body.input === undefined ? null : body.input);
|
|
48934
|
+
var connectionPayload = buildConnectionPayload(body);
|
|
48935
|
+
var inputIsLarge = Buffer.byteLength(inputJson, "utf8") > INLINE_INPUT_LIMIT_BYTES;
|
|
48936
|
+
var needsTempDir = inputIsLarge || connectionPayload !== null;
|
|
48937
|
+
var tempDir = null;
|
|
48938
|
+
var env = Object.assign({}, process.env);
|
|
48939
|
+
try {
|
|
48940
|
+
if (needsTempDir) {
|
|
48941
|
+
tempDir = path.join(os.tmpdir(), "plugin-run-" + Date.now() + "-" + Math.random().toString(36).slice(2));
|
|
48942
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
48943
|
+
}
|
|
48944
|
+
if (inputIsLarge) {
|
|
48945
|
+
var inputFile = path.join(tempDir, "input.json");
|
|
48946
|
+
fs.writeFileSync(inputFile, inputJson);
|
|
48947
|
+
env.PLUGIN_INPUT_FILE = inputFile;
|
|
48948
|
+
} else {
|
|
48949
|
+
env.PLUGIN_INPUT = encodeInput(body.input);
|
|
48950
|
+
}
|
|
48951
|
+
if (connectionPayload !== null) {
|
|
48952
|
+
var connectionFile = path.join(tempDir, "connection.json");
|
|
48953
|
+
fs.writeFileSync(connectionFile, JSON.stringify(connectionPayload));
|
|
48954
|
+
env.PLUGIN_CONNECTION_FILE = connectionFile;
|
|
48955
|
+
}
|
|
48956
|
+
env.PLUGIN_CONTEXT = encodeInput(body.context || {});
|
|
48957
|
+
var handlerTimeoutMs = resolveHandlerTimeoutMs(body.timeoutMs);
|
|
48958
|
+
var exec = await runNode(handlerPath, pluginDir, env, handlerTimeoutMs);
|
|
48959
|
+
if (exec.error) {
|
|
48960
|
+
return failureFromExec(exec, command);
|
|
48961
|
+
}
|
|
48962
|
+
return parseHandlerEnvelope(exec.stdout);
|
|
48963
|
+
} finally {
|
|
48964
|
+
if (tempDir) {
|
|
48965
|
+
try {
|
|
48966
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
48967
|
+
} catch (err) {
|
|
48968
|
+
// best-effort cleanup
|
|
48969
|
+
}
|
|
48970
|
+
}
|
|
48971
|
+
}
|
|
48972
|
+
}
|
|
48973
|
+
|
|
48974
|
+
function manifestHash() {
|
|
48975
|
+
try {
|
|
48976
|
+
var content = fs.readFileSync(path.join(PLUGINS_ROOT, config.type, "plugin.json"), "utf8");
|
|
48977
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
48978
|
+
} catch (err) {
|
|
48979
|
+
return "";
|
|
48980
|
+
}
|
|
48981
|
+
}
|
|
48982
|
+
|
|
48983
|
+
function sendJson(res, status, payload) {
|
|
48984
|
+
var body = JSON.stringify(payload);
|
|
48985
|
+
res.writeHead(status, {
|
|
48986
|
+
"content-type": "application/json",
|
|
48987
|
+
"content-length": Buffer.byteLength(body),
|
|
48988
|
+
});
|
|
48989
|
+
res.end(body);
|
|
48990
|
+
}
|
|
48991
|
+
|
|
48992
|
+
function handleRequest(req, res) {
|
|
48993
|
+
var url = String(req.url || "").split("?")[0];
|
|
48994
|
+
if (req.method === "GET" && url === "/health") {
|
|
48995
|
+
sendJson(res, 200, { ok: true, version: VERSION });
|
|
48996
|
+
return;
|
|
48997
|
+
}
|
|
48998
|
+
if (req.method === "GET" && url === "/meta") {
|
|
48999
|
+
sendJson(res, 200, { type: config.type, manifestHash: manifestHash() });
|
|
49000
|
+
return;
|
|
49001
|
+
}
|
|
49002
|
+
if (req.method === "POST" && url === "/invoke") {
|
|
49003
|
+
readJsonBody(req)
|
|
49004
|
+
.then(function (body) {
|
|
49005
|
+
if (!isRecord(body)) {
|
|
49006
|
+
sendJson(res, 400, { ok: false, error: "invalid body" });
|
|
49007
|
+
return;
|
|
49008
|
+
}
|
|
49009
|
+
return runHandler(body).then(function (result) {
|
|
49010
|
+
sendJson(res, 200, result);
|
|
49011
|
+
});
|
|
49012
|
+
})
|
|
49013
|
+
.catch(function (err) {
|
|
49014
|
+
sendJson(res, 400, { ok: false, error: String(err && err.message ? err.message : err) });
|
|
49015
|
+
});
|
|
49016
|
+
return;
|
|
49017
|
+
}
|
|
49018
|
+
sendJson(res, 404, { ok: false, error: "not found" });
|
|
49019
|
+
}
|
|
49020
|
+
|
|
49021
|
+
var config = parseHostArgs(process.argv);
|
|
49022
|
+
var server = http.createServer(handleRequest);
|
|
49023
|
+
server.listen(config.port, "0.0.0.0", function () {
|
|
49024
|
+
process.stdout.write(
|
|
49025
|
+
"[plugin-host] listening type=" + config.type + " port=" + config.port + " version=" + VERSION + "\n"
|
|
49026
|
+
);
|
|
49027
|
+
});
|
|
49028
|
+
`;
|
|
49029
|
+
function pluginHostContentHash() {
|
|
49030
|
+
return createHash8("sha256").update(`${PLUGIN_HOST_VERSION}:${PLUGIN_HOST_SERVER_SOURCE}`).digest("hex").slice(0, 16);
|
|
49031
|
+
}
|
|
49032
|
+
|
|
49033
|
+
// src/sandbox_plugin/host/pluginHostInstaller.ts
|
|
49034
|
+
var PLUGIN_HOST_DIR = "/root/.agents/.plugin-host";
|
|
49035
|
+
var SERVER_PATH = `${PLUGIN_HOST_DIR}/server.js`;
|
|
49036
|
+
var VERSION_PATH = `${PLUGIN_HOST_DIR}/version.json`;
|
|
49037
|
+
async function installPluginHost(sandbox) {
|
|
49038
|
+
const hash = pluginHostContentHash();
|
|
49039
|
+
try {
|
|
49040
|
+
const existing = await sandbox.file.readFile(VERSION_PATH);
|
|
49041
|
+
const parsed = JSON.parse(existing.content);
|
|
49042
|
+
if (parsed.hash === hash) {
|
|
49043
|
+
return { path: SERVER_PATH, version: PLUGIN_HOST_VERSION, hash, changed: false };
|
|
49044
|
+
}
|
|
49045
|
+
} catch {
|
|
49046
|
+
}
|
|
49047
|
+
await sandbox.file.createDirectory(PLUGIN_HOST_DIR).catch(() => void 0);
|
|
49048
|
+
await sandbox.file.writeFile(SERVER_PATH, PLUGIN_HOST_SERVER_SOURCE);
|
|
49049
|
+
await sandbox.file.writeFile(VERSION_PATH, JSON.stringify({ version: PLUGIN_HOST_VERSION, hash }, null, 2));
|
|
49050
|
+
return { path: SERVER_PATH, version: PLUGIN_HOST_VERSION, hash, changed: true };
|
|
49051
|
+
}
|
|
49052
|
+
|
|
47308
49053
|
// src/personal_assistant/PersonalAssistantConfig.ts
|
|
47309
49054
|
function deepClone(obj) {
|
|
47310
49055
|
return JSON.parse(JSON.stringify(obj));
|
|
@@ -47689,16 +49434,16 @@ var IdRemapper = class {
|
|
|
47689
49434
|
};
|
|
47690
49435
|
|
|
47691
49436
|
// src/util/directRoomId.ts
|
|
47692
|
-
import { createHash as
|
|
49437
|
+
import { createHash as createHash9 } from "crypto";
|
|
47693
49438
|
function generateDirectHumanProjectId(tenantId2, workspaceId, userId1, userId2) {
|
|
47694
49439
|
const sortedUserIds = [userId1, userId2].sort();
|
|
47695
49440
|
const parts = [tenantId2, workspaceId, ...sortedUserIds];
|
|
47696
|
-
const hash =
|
|
49441
|
+
const hash = createHash9("sha256").update(parts.join(":")).digest("hex");
|
|
47697
49442
|
return `direct_human_${hash}`;
|
|
47698
49443
|
}
|
|
47699
49444
|
function generateDirectBotProjectId(tenantId2, workspaceId, userId, assistantId) {
|
|
47700
49445
|
const parts = [tenantId2, workspaceId, userId, assistantId];
|
|
47701
|
-
const hash =
|
|
49446
|
+
const hash = createHash9("sha256").update(parts.join(":")).digest("hex");
|
|
47702
49447
|
return `direct_bot_${hash}`;
|
|
47703
49448
|
}
|
|
47704
49449
|
function parseDirectRoomType(projectId) {
|
|
@@ -47727,6 +49472,7 @@ export {
|
|
|
47727
49472
|
CapabilityBundleValidationError,
|
|
47728
49473
|
CapabilityToolNameConflictError,
|
|
47729
49474
|
CapabilityToolRegistry,
|
|
49475
|
+
ChannelConnectionLeader,
|
|
47730
49476
|
ChunkBuffer,
|
|
47731
49477
|
ChunkBufferLatticeManager,
|
|
47732
49478
|
CollectionLatticeManager,
|
|
@@ -47738,7 +49484,9 @@ export {
|
|
|
47738
49484
|
DEFAULT_CALIBRATION_PROBES,
|
|
47739
49485
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
47740
49486
|
DEFAULT_DELAY_MS,
|
|
49487
|
+
DEFAULT_HANDLER_TIMEOUT_MS,
|
|
47741
49488
|
DEFAULT_MAX_ATTEMPTS,
|
|
49489
|
+
DEFAULT_RESIDENT_INVOKE_TIMEOUT_MS,
|
|
47742
49490
|
DaytonaInstance,
|
|
47743
49491
|
DaytonaProvider,
|
|
47744
49492
|
DefaultScheduleClient,
|
|
@@ -47756,6 +49504,7 @@ export {
|
|
|
47756
49504
|
HEALTH_CHECK_INTERVAL_MS,
|
|
47757
49505
|
HEALTH_CHECK_TIMEOUT_MS,
|
|
47758
49506
|
HumanMessage7 as HumanMessage,
|
|
49507
|
+
INLINE_INPUT_LIMIT_BYTES2 as INLINE_INPUT_LIMIT_BYTES,
|
|
47759
49508
|
IdRemapper,
|
|
47760
49509
|
InMemoryA2AApiKeyStore,
|
|
47761
49510
|
InMemoryAgentWebAppStore,
|
|
@@ -47796,10 +49545,12 @@ export {
|
|
|
47796
49545
|
LocalSandboxProvider,
|
|
47797
49546
|
LoggerLatticeManager,
|
|
47798
49547
|
MAX_BACKOFF_MS,
|
|
49548
|
+
MAX_HANDLER_TIMEOUT_MS,
|
|
47799
49549
|
MAX_LINE_LENGTH,
|
|
47800
49550
|
MAX_PACK_ENTRIES,
|
|
47801
49551
|
MAX_PACK_FILE_BYTES,
|
|
47802
49552
|
MAX_PACK_TOTAL_BYTES,
|
|
49553
|
+
MIN_HANDLER_TIMEOUT_MS,
|
|
47803
49554
|
ManagedTaskStartError,
|
|
47804
49555
|
McpLatticeManager,
|
|
47805
49556
|
MemoryBackend,
|
|
@@ -47818,8 +49569,12 @@ export {
|
|
|
47818
49569
|
PACK_DIRECTORY_TIMEOUT_SECONDS,
|
|
47819
49570
|
PLATFORM_SECRET_ENV_VARS,
|
|
47820
49571
|
PLUGIN_CATALOG_CHANGED_TOPIC,
|
|
49572
|
+
PLUGIN_HOST_DIR,
|
|
49573
|
+
PLUGIN_HOST_SERVER_SOURCE,
|
|
49574
|
+
PLUGIN_HOST_VERSION,
|
|
47821
49575
|
PersonalAssistantConfig,
|
|
47822
49576
|
PinoLoggerClient,
|
|
49577
|
+
PluginDeploymentRuntimeRegistry,
|
|
47823
49578
|
PluginRegistry,
|
|
47824
49579
|
PostgresDatabase,
|
|
47825
49580
|
ProjectBotMembershipIdConflictError,
|
|
@@ -47881,7 +49636,9 @@ export {
|
|
|
47881
49636
|
appendUiFence,
|
|
47882
49637
|
assertSafeUpstreamUrl,
|
|
47883
49638
|
buildBundleFromDist,
|
|
49639
|
+
buildConnectionPayload2 as buildConnectionPayload,
|
|
47884
49640
|
buildGrepResultsDict,
|
|
49641
|
+
buildHandlerCommand,
|
|
47885
49642
|
buildInput,
|
|
47886
49643
|
buildNamedVolumeName,
|
|
47887
49644
|
buildRemotePackDirectoryCommand,
|
|
@@ -47949,9 +49706,11 @@ export {
|
|
|
47949
49706
|
decrypt,
|
|
47950
49707
|
decryptConfigFields,
|
|
47951
49708
|
describeCronExpression,
|
|
49709
|
+
disconnectAllChannels,
|
|
47952
49710
|
documentLearningPlugin,
|
|
47953
49711
|
documentParserPlugin,
|
|
47954
49712
|
embeddingsLatticeManager,
|
|
49713
|
+
encodeInput,
|
|
47955
49714
|
encrypt,
|
|
47956
49715
|
encryptConfigFields,
|
|
47957
49716
|
ensureBuiltinAgentsForTenant,
|
|
@@ -47995,6 +49754,7 @@ export {
|
|
|
47995
49754
|
getModelLattice,
|
|
47996
49755
|
getNextCronTime,
|
|
47997
49756
|
getOrCreateCollectionVectorStore,
|
|
49757
|
+
getPluginDeploymentService,
|
|
47998
49758
|
getPluginSkillManifestVersion,
|
|
47999
49759
|
getQueueLattice,
|
|
48000
49760
|
getReplicaId,
|
|
@@ -48020,9 +49780,12 @@ export {
|
|
|
48020
49780
|
globSearchFiles,
|
|
48021
49781
|
grepMatchesFromFiles,
|
|
48022
49782
|
grepSearchFiles,
|
|
49783
|
+
handlerResultText,
|
|
48023
49784
|
hasChunkBuffer,
|
|
48024
49785
|
hasDelegatedWorkerToken,
|
|
48025
49786
|
hasRuntimeCapabilityPolicy,
|
|
49787
|
+
installPluginHost,
|
|
49788
|
+
invokeResident,
|
|
48026
49789
|
invokeWithRetry,
|
|
48027
49790
|
isBuiltInSkill,
|
|
48028
49791
|
isDirectRoom,
|
|
@@ -48050,6 +49813,8 @@ export {
|
|
|
48050
49813
|
parallelLimit,
|
|
48051
49814
|
parseCronExpression,
|
|
48052
49815
|
parseDirectRoomType,
|
|
49816
|
+
parseHandlerEnvelope,
|
|
49817
|
+
parseHostArgs,
|
|
48053
49818
|
parseJudgeVerdict,
|
|
48054
49819
|
parsePackSize,
|
|
48055
49820
|
parseQueuedExecutionMode,
|
|
@@ -48058,6 +49823,7 @@ export {
|
|
|
48058
49823
|
parseTrustedRunContext,
|
|
48059
49824
|
parseYaml,
|
|
48060
49825
|
performStringReplacement,
|
|
49826
|
+
pluginHostContentHash,
|
|
48061
49827
|
postProcessTaskMutationResponse,
|
|
48062
49828
|
prepareTaskDispatch,
|
|
48063
49829
|
projectMutationMutex,
|
|
@@ -48096,6 +49862,7 @@ export {
|
|
|
48096
49862
|
renderTemplate,
|
|
48097
49863
|
resetReplicaIdForTests,
|
|
48098
49864
|
resolveEnabledPluginSkill,
|
|
49865
|
+
resolveHandlerTimeoutMs,
|
|
48099
49866
|
resolveJudgeModelKey,
|
|
48100
49867
|
resolvePath,
|
|
48101
49868
|
resolvePluginConnections,
|
|
@@ -48115,6 +49882,7 @@ export {
|
|
|
48115
49882
|
setBindingRegistry,
|
|
48116
49883
|
setEvalRunService,
|
|
48117
49884
|
setMenuRegistry,
|
|
49885
|
+
setPluginDeploymentService,
|
|
48118
49886
|
setSandboxPluginCatalog,
|
|
48119
49887
|
skillLatticeManager,
|
|
48120
49888
|
sqlDatabaseManager,
|