@axiom-lattice/core 5.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -50,6 +50,17 @@ var ModelLattice = class extends BaseChatModel {
50
50
  this.config = config;
51
51
  this.llm = this.initChatModel(config);
52
52
  }
53
+ /**
54
+ * 返回构造时使用的配置
55
+ *
56
+ * 供调用方(如 models 控制器)读取 apiKey 是否存在、以及展示元数据,
57
+ * 避免反射读取私有字段。
58
+ *
59
+ * @returns 构造时传入的 LLMConfig
60
+ */
61
+ getConfig() {
62
+ return this.config;
63
+ }
53
64
  /**
54
65
  * 返回模型类型
55
66
  */
@@ -189,6 +200,9 @@ var ModelLattice = class extends BaseChatModel {
189
200
  maxRetries: config.maxRetries || 2,
190
201
  streaming: config.streaming,
191
202
  reasoning: { effort: "none" },
203
+ // Responses API and Chat Completions are different paths with different
204
+ // request fields; an endpoint that supports only one rejects the other.
205
+ ...config.useResponsesApi ? { useResponsesApi: true } : {},
192
206
  apiKey: config.apiKey || process.env[config.apiKeyEnvName || "OPENAI_API_KEY"],
193
207
  configuration: {
194
208
  baseURL: config.baseURL
@@ -279,9 +293,74 @@ var ModelLatticeManager = class _ModelLatticeManager extends BaseLatticeManager
279
293
  getLatticeKeys() {
280
294
  return this.keys();
281
295
  }
296
+ // ========== 租户维度 API ==========
297
+ // 键由基类拼装为 models:{tenantId}:{key}。注意 "default" 租户的键与旧版
298
+ // key-only API 完全一致(models:default:{key}),因此对该租户是逐字节兼容的。
299
+ /**
300
+ * 以租户维度注册模型Lattice
301
+ *
302
+ * 同名 key 采用「后写覆盖」语义:先移除再注册,避免基类重复注册时抛错。
303
+ *
304
+ * @param tenantId 租户ID
305
+ * @param key Lattice键名
306
+ * @param config 模型配置
307
+ */
308
+ registerLatticeWithTenant(tenantId2, key5, config) {
309
+ const client = new ModelLattice(config);
310
+ const modelLattice = {
311
+ key: key5,
312
+ client
313
+ };
314
+ if (this.hasWithTenant(tenantId2, key5)) {
315
+ this.removeWithTenant(tenantId2, key5);
316
+ }
317
+ this.registerWithTenant(tenantId2, key5, modelLattice);
318
+ }
319
+ /**
320
+ * 获取指定租户的模型Lattice
321
+ * @param tenantId 租户ID
322
+ * @param key Lattice键名
323
+ */
324
+ getModelLatticeWithTenant(tenantId2, key5) {
325
+ return this.getWithTenant(tenantId2, key5);
326
+ }
327
+ /**
328
+ * 检查指定租户的模型Lattice是否存在
329
+ * @param tenantId 租户ID
330
+ * @param key Lattice键名
331
+ */
332
+ hasLatticeWithTenant(tenantId2, key5) {
333
+ return this.hasWithTenant(tenantId2, key5);
334
+ }
335
+ /**
336
+ * 获取指定租户的所有模型Lattice
337
+ * @param tenantId 租户ID
338
+ */
339
+ getAllLatticesByTenant(tenantId2) {
340
+ return this.getAllByTenant(tenantId2);
341
+ }
342
+ /**
343
+ * 获取指定租户的所有模型键名
344
+ *
345
+ * 只返回键名,不构造 Lattice 对象数组,适合仅需判断存在性的调用方。
346
+ *
347
+ * @param tenantId 租户ID
348
+ */
349
+ getLatticeKeysByTenant(tenantId2) {
350
+ return this.keysByTenant(tenantId2);
351
+ }
352
+ /**
353
+ * 移除指定租户的模型Lattice
354
+ * @param tenantId 租户ID
355
+ * @param key Lattice键名
356
+ */
357
+ removeLatticeWithTenant(tenantId2, key5) {
358
+ return this.removeWithTenant(tenantId2, key5);
359
+ }
282
360
  };
283
361
  var modelLatticeManager = ModelLatticeManager.getInstance();
284
362
  var registerModelLattice = (key5, config) => modelLatticeManager.registerLattice(key5, config);
363
+ var registerModelLatticeForTenant = (tenantId2, key5, config) => modelLatticeManager.registerLatticeWithTenant(tenantId2, key5, config);
285
364
  var getModelLattice = (key5) => modelLatticeManager.getModelLattice(key5);
286
365
 
287
366
  // src/tool_lattice/get_current_date_time/index.ts
@@ -2013,6 +2092,240 @@ var InMemoryMcpServerConfigStore = class {
2013
2092
  }
2014
2093
  };
2015
2094
 
2095
+ // src/store_lattice/InMemoryModelProviderStore.ts
2096
+ var InMemoryModelProviderStore = class {
2097
+ constructor() {
2098
+ this.providers = /* @__PURE__ */ new Map();
2099
+ this.models = /* @__PURE__ */ new Map();
2100
+ }
2101
+ /**
2102
+ * Get composite key for providers
2103
+ */
2104
+ getProviderKey(tenantId2, id) {
2105
+ return `${tenantId2}:${id}`;
2106
+ }
2107
+ /**
2108
+ * Get composite key for snapshot models
2109
+ */
2110
+ getModelKey(tenantId2, providerId, modelId) {
2111
+ return `${tenantId2}:${providerId}:${modelId}`;
2112
+ }
2113
+ /**
2114
+ * Get all provider configurations for a tenant
2115
+ */
2116
+ async listProviders(tenantId2) {
2117
+ return Array.from(this.providers.values()).filter(
2118
+ (provider) => provider.tenantId === tenantId2
2119
+ );
2120
+ }
2121
+ /**
2122
+ * Get a provider configuration by ID
2123
+ */
2124
+ async getProviderById(tenantId2, id) {
2125
+ return this.providers.get(this.getProviderKey(tenantId2, id)) || null;
2126
+ }
2127
+ /**
2128
+ * Get a provider configuration by business name
2129
+ */
2130
+ async getProviderByName(tenantId2, name) {
2131
+ const providers = await this.listProviders(tenantId2);
2132
+ return providers.find((provider) => provider.name === name) || null;
2133
+ }
2134
+ /**
2135
+ * Create a new provider configuration
2136
+ */
2137
+ async createProvider(tenantId2, id, data) {
2138
+ const now = /* @__PURE__ */ new Date();
2139
+ const entry = {
2140
+ id,
2141
+ tenantId: tenantId2,
2142
+ name: data.name,
2143
+ displayName: data.displayName,
2144
+ protocol: data.protocol,
2145
+ apiStyle: data.apiStyle ?? "chat-completions",
2146
+ // Phase 1 pins the LLM provider: the UI does not offer a choice, and some
2147
+ // initChatModel branches hardcode their own baseURL and would silently
2148
+ // ignore the tenant's (see ADR-138 decision 7b).
2149
+ llmProvider: "openai",
2150
+ baseURL: data.baseURL,
2151
+ apiKey: data.apiKey || void 0,
2152
+ apiKeyHint: data.apiKey ? data.apiKey.slice(-4) : void 0,
2153
+ enabled: data.enabled ?? true,
2154
+ status: "unknown",
2155
+ createdAt: now,
2156
+ updatedAt: now
2157
+ };
2158
+ this.providers.set(this.getProviderKey(tenantId2, id), entry);
2159
+ return entry;
2160
+ }
2161
+ /**
2162
+ * Update an existing provider configuration.
2163
+ * An omitted or empty `apiKey` leaves the stored key untouched.
2164
+ */
2165
+ async updateProvider(tenantId2, id, updates) {
2166
+ const key5 = this.getProviderKey(tenantId2, id);
2167
+ const existing = this.providers.get(key5);
2168
+ if (!existing) {
2169
+ return null;
2170
+ }
2171
+ const hasNewApiKey = Boolean(updates.apiKey);
2172
+ const clearsApiKey = updates.clearApiKey === true;
2173
+ const updated = {
2174
+ ...existing,
2175
+ name: updates.name ?? existing.name,
2176
+ displayName: updates.displayName !== void 0 ? updates.displayName : existing.displayName,
2177
+ protocol: updates.protocol ?? existing.protocol,
2178
+ apiStyle: updates.apiStyle ?? existing.apiStyle,
2179
+ baseURL: updates.baseURL ?? existing.baseURL,
2180
+ apiKey: clearsApiKey ? void 0 : hasNewApiKey ? updates.apiKey : existing.apiKey,
2181
+ apiKeyHint: clearsApiKey ? void 0 : hasNewApiKey ? updates.apiKey?.slice(-4) : existing.apiKeyHint,
2182
+ enabled: updates.enabled ?? existing.enabled,
2183
+ updatedAt: /* @__PURE__ */ new Date()
2184
+ };
2185
+ this.providers.set(key5, updated);
2186
+ return updated;
2187
+ }
2188
+ /**
2189
+ * Delete a provider configuration and its model snapshot
2190
+ */
2191
+ async deleteProvider(tenantId2, id) {
2192
+ await this.deleteModelsByProvider(tenantId2, id);
2193
+ return this.providers.delete(this.getProviderKey(tenantId2, id));
2194
+ }
2195
+ /**
2196
+ * Record the outcome of a discovery attempt
2197
+ */
2198
+ async markProviderStatus(tenantId2, id, status, lastError, discoveredAt) {
2199
+ const key5 = this.getProviderKey(tenantId2, id);
2200
+ const existing = this.providers.get(key5);
2201
+ if (!existing) {
2202
+ return;
2203
+ }
2204
+ this.providers.set(key5, {
2205
+ ...existing,
2206
+ status,
2207
+ lastError: status === "ok" ? void 0 : lastError,
2208
+ lastDiscoveredAt: discoveredAt ?? existing.lastDiscoveredAt,
2209
+ updatedAt: /* @__PURE__ */ new Date()
2210
+ });
2211
+ }
2212
+ /**
2213
+ * List snapshot models for a tenant
2214
+ */
2215
+ async listModels(tenantId2, providerId) {
2216
+ return Array.from(this.models.values()).filter(
2217
+ (model) => model.tenantId === tenantId2 && (providerId === void 0 || model.providerId === providerId)
2218
+ );
2219
+ }
2220
+ /**
2221
+ * Get a single snapshot model.
2222
+ * Keyed by provider as well as model id, since two providers in the same
2223
+ * tenant may expose the same upstream model id.
2224
+ */
2225
+ async getModel(tenantId2, providerId, modelId) {
2226
+ return this.models.get(this.getModelKey(tenantId2, providerId, modelId)) || null;
2227
+ }
2228
+ /**
2229
+ * Replace a provider's model snapshot with the result of one discovery.
2230
+ *
2231
+ * Model ids are scoped per provider, so the same upstream id may appear
2232
+ * under several providers in one tenant — each is an independent model.
2233
+ *
2234
+ * Models the upstream no longer offers are removed; models that survive keep
2235
+ * the tenant's enable/disable choice rather than being reset.
2236
+ */
2237
+ async replaceProviderModels(tenantId2, providerId, models) {
2238
+ const previous = await this.listModels(tenantId2, providerId);
2239
+ const previousById = new Map(
2240
+ previous.map((model) => [model.modelId, model])
2241
+ );
2242
+ const incomingIds = new Set(models.map((model) => model.id));
2243
+ for (const model of previous) {
2244
+ if (!incomingIds.has(model.modelId)) {
2245
+ this.models.delete(
2246
+ this.getModelKey(tenantId2, providerId, model.modelId)
2247
+ );
2248
+ }
2249
+ }
2250
+ const now = /* @__PURE__ */ new Date();
2251
+ const upserted = [];
2252
+ for (const model of models) {
2253
+ const existing = previousById.get(model.id);
2254
+ const entry = {
2255
+ id: `${providerId}:${model.id}`,
2256
+ tenantId: tenantId2,
2257
+ providerId,
2258
+ modelId: model.id,
2259
+ displayName: model.displayName,
2260
+ ownedBy: model.ownedBy,
2261
+ upstreamCreatedAt: model.createdAt ? new Date(model.createdAt) : void 0,
2262
+ // An explicit `enabled` from the caller wins; discovery omits it and
2263
+ // the tenant's earlier choice is preserved.
2264
+ enabled: model.enabled ?? existing?.enabled ?? true,
2265
+ // Unlike `enabled`, this is not preserved: a model present in the list
2266
+ // the caller just read is, by definition, no longer stale. Only an
2267
+ // explicit flag marks one.
2268
+ stale: model.stale ?? false,
2269
+ discoveredAt: now
2270
+ };
2271
+ this.models.set(this.getModelKey(tenantId2, providerId, model.id), entry);
2272
+ upserted.push(model.id);
2273
+ }
2274
+ return upserted;
2275
+ }
2276
+ /**
2277
+ * Enable or disable a single snapshot model
2278
+ */
2279
+ async setModelEnabled(tenantId2, providerId, modelId, enabled) {
2280
+ const key5 = this.getModelKey(tenantId2, providerId, modelId);
2281
+ const existing = this.models.get(key5);
2282
+ if (!existing) {
2283
+ return null;
2284
+ }
2285
+ const updated = {
2286
+ ...existing,
2287
+ enabled
2288
+ };
2289
+ this.models.set(key5, updated);
2290
+ return updated;
2291
+ }
2292
+ /**
2293
+ * Delete every snapshot model belonging to a provider
2294
+ */
2295
+ async deleteModelsByProvider(tenantId2, providerId) {
2296
+ let deleted = 0;
2297
+ for (const [key5, model] of Array.from(this.models.entries())) {
2298
+ if (model.tenantId === tenantId2 && model.providerId === providerId) {
2299
+ this.models.delete(key5);
2300
+ deleted += 1;
2301
+ }
2302
+ }
2303
+ return deleted;
2304
+ }
2305
+ /**
2306
+ * Clear all providers and models (useful for testing)
2307
+ */
2308
+ clear() {
2309
+ this.providers.clear();
2310
+ this.models.clear();
2311
+ }
2312
+ /**
2313
+ * Clear providers and models for a specific tenant
2314
+ */
2315
+ clearByTenant(tenantId2) {
2316
+ for (const [key5, provider] of Array.from(this.providers.entries())) {
2317
+ if (provider.tenantId === tenantId2) {
2318
+ this.providers.delete(key5);
2319
+ }
2320
+ }
2321
+ for (const [key5, model] of Array.from(this.models.entries())) {
2322
+ if (model.tenantId === tenantId2) {
2323
+ this.models.delete(key5);
2324
+ }
2325
+ }
2326
+ }
2327
+ };
2328
+
2016
2329
  // src/store_lattice/InMemoryAgentWebAppStore.ts
2017
2330
  import { randomUUID } from "crypto";
2018
2331
  function cloneAppearance(appearance) {
@@ -4501,6 +4814,7 @@ var defaultProjectStore = new InMemoryProjectStore(defaultCapabilityBundleCoordi
4501
4814
  var defaultDatabaseConfigStore = new InMemoryDatabaseConfigStore();
4502
4815
  var defaultMetricsServerConfigStore = new InMemoryMetricsServerConfigStore();
4503
4816
  var defaultMcpServerConfigStore = new InMemoryMcpServerConfigStore();
4817
+ var defaultModelProviderStore = new InMemoryModelProviderStore();
4504
4818
  var defaultAgentWebAppStore = new InMemoryAgentWebAppStore();
4505
4819
  var defaultCapabilityBundleStore = new InMemoryCapabilityBundleStore(defaultProjectStore, defaultCapabilityBundleCoordinator);
4506
4820
  var defaultProjectRoomCoordinator = new InMemoryProjectRoomCoordinator();
@@ -4522,6 +4836,7 @@ storeLatticeManager.registerLattice("default", "project", defaultProjectStore);
4522
4836
  storeLatticeManager.registerLattice("default", "database", defaultDatabaseConfigStore);
4523
4837
  storeLatticeManager.registerLattice("default", "metrics", defaultMetricsServerConfigStore);
4524
4838
  storeLatticeManager.registerLattice("default", "mcp", defaultMcpServerConfigStore);
4839
+ storeLatticeManager.registerLattice("default", "modelProvider", defaultModelProviderStore);
4525
4840
  storeLatticeManager.registerLattice("default", "agentWebApp", defaultAgentWebAppStore);
4526
4841
  storeLatticeManager.registerLattice("default", "capabilityBundle", defaultCapabilityBundleStore);
4527
4842
  storeLatticeManager.registerLattice("default", "projectRoom", defaultProjectRoomStore);
@@ -28847,7 +29162,7 @@ var agentArchitectConfig = {
28847
29162
  "delete_agent",
28848
29163
  "manage_binding"
28849
29164
  ],
28850
- subAgents: ["document-parser-benchmark"],
29165
+ subAgents: ["document-parser", "document-parser-benchmark"],
28851
29166
  middleware: [
28852
29167
  {
28853
29168
  id: "filesystem",
@@ -30552,6 +30867,7 @@ async function removeCollectionVectorStore(name, tenantId2) {
30552
30867
 
30553
30868
  // src/mcp_lattice/McpLatticeManager.ts
30554
30869
  import { MultiServerMCPClient } from "@langchain/mcp-adapters";
30870
+ import { DynamicStructuredTool as DynamicStructuredTool2 } from "@langchain/core/tools";
30555
30871
 
30556
30872
  // src/mcp_lattice/reconnectPolicy.ts
30557
30873
  var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
@@ -30560,6 +30876,13 @@ var DEFAULT_DELAY_MS = 1e3;
30560
30876
  var MAX_BACKOFF_MS = 3e4;
30561
30877
  var HEALTH_CHECK_INTERVAL_MS = 3e4;
30562
30878
  var HEALTH_CHECK_TIMEOUT_MS = 5e3;
30879
+ function isMcpConnectionError(error) {
30880
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
30881
+ if (!message) return false;
30882
+ return /not connected|connection closed|session not found|no session|maximum reconnection/i.test(
30883
+ message
30884
+ );
30885
+ }
30563
30886
  function nextBackoffDelay(attempt, base = DEFAULT_DELAY_MS, max = MAX_BACKOFF_MS) {
30564
30887
  if (attempt <= 0) return base;
30565
30888
  return Math.min(base * Math.pow(2, attempt - 1), max);
@@ -30584,12 +30907,21 @@ async function withTimeout(operation, ms, onTimeout) {
30584
30907
 
30585
30908
  // src/mcp_lattice/McpLatticeManager.ts
30586
30909
  var realClientFactory = ({ mcpServers }) => new MultiServerMCPClient({ mcpServers });
30910
+ function extractRawCall(tool68) {
30911
+ const rawCall = tool68._call;
30912
+ if (typeof rawCall === "function") return rawCall.bind(tool68);
30913
+ return async (...args) => {
30914
+ const invoke = tool68.invoke.bind(tool68);
30915
+ return invoke(args[0], args[2]);
30916
+ };
30917
+ }
30587
30918
  var McpLatticeManager = class _McpLatticeManager extends BaseLatticeManager {
30588
30919
  constructor() {
30589
30920
  super(...arguments);
30590
30921
  this.runtimes = /* @__PURE__ */ new Map();
30591
30922
  this.clientFactory = realClientFactory;
30592
30923
  this.healthCheckInFlight = false;
30924
+ this.reconnectPromises = /* @__PURE__ */ new Map();
30593
30925
  }
30594
30926
  static getInstance() {
30595
30927
  if (!_McpLatticeManager._instance) {
@@ -30847,6 +31179,64 @@ var McpLatticeManager = class _McpLatticeManager extends BaseLatticeManager {
30847
31179
  rt.attempt = 0;
30848
31180
  await this.reconnectRuntime(rt);
30849
31181
  }
31182
+ /**
31183
+ * Single-flight a forced reconnect per `(tenantId, serverKey)` so concurrent
31184
+ * tool calls that all hit the same dead session reconnect once, not once each.
31185
+ */
31186
+ reconnectShared(rt) {
31187
+ const key5 = this.compositeKey(rt.id.tenantId, rt.id.serverKey);
31188
+ const existing = this.reconnectPromises.get(key5);
31189
+ if (existing) return existing;
31190
+ const work = this.reconnectServer(rt.id.tenantId, rt.id.serverKey).finally(() => {
31191
+ if (this.reconnectPromises.get(key5) === work) this.reconnectPromises.delete(key5);
31192
+ });
31193
+ this.reconnectPromises.set(key5, work);
31194
+ return work;
31195
+ }
31196
+ /**
31197
+ * Wrap a raw MCP tool so a call landing on a torn-down session
31198
+ * (`Not connected` / `Connection closed` / session expiry) transparently
31199
+ * forces a reconnect and retries once against the fresh client, instead of
31200
+ * failing the surrounding run. Non-connection errors propagate untouched.
31201
+ */
31202
+ wrapToolForResilience(rt, raw) {
31203
+ const call = extractRawCall(raw);
31204
+ return new DynamicStructuredTool2({
31205
+ name: raw.name,
31206
+ description: raw.description,
31207
+ schema: raw.schema,
31208
+ responseFormat: raw.responseFormat,
31209
+ defaultConfig: raw.defaultConfig,
31210
+ metadata: raw.metadata,
31211
+ returnDirect: raw.returnDirect,
31212
+ func: async (...args) => this.invokeResilient(rt, raw.name, call, args)
31213
+ });
31214
+ }
31215
+ async invokeResilient(rt, toolName2, call, args) {
31216
+ try {
31217
+ await this.ensureConnected(rt.id.tenantId, rt.id.serverKey);
31218
+ return await call(...args);
31219
+ } catch (error) {
31220
+ if (!isMcpConnectionError(error) || !this.isActive(rt)) throw error;
31221
+ console.warn(
31222
+ `[MCP] ${rt.id.tenantId}/${rt.id.serverKey}: tool "${toolName2}" hit a dead session; reconnecting and retrying once`,
31223
+ error instanceof Error ? error.message : error
31224
+ );
31225
+ try {
31226
+ await this.reconnectShared(rt);
31227
+ } catch (reconnectError) {
31228
+ console.warn(
31229
+ `[MCP] ${rt.id.tenantId}/${rt.id.serverKey}: reconnect for tool "${toolName2}" failed`,
31230
+ reconnectError instanceof Error ? reconnectError.message : reconnectError
31231
+ );
31232
+ throw error;
31233
+ }
31234
+ const fresh = await this.getRawTools(rt.id.tenantId, rt.id.serverKey);
31235
+ const match = fresh.find((t) => t.name === toolName2);
31236
+ if (!match) throw error;
31237
+ return await extractRawCall(match)(...args);
31238
+ }
31239
+ }
30850
31240
  async checkHealth() {
30851
31241
  if (this.healthCheckInFlight) return;
30852
31242
  this.healthCheckInFlight = true;
@@ -30865,8 +31255,12 @@ var McpLatticeManager = class _McpLatticeManager extends BaseLatticeManager {
30865
31255
  await withTimeout(session.listTools(), HEALTH_CHECK_TIMEOUT_MS, async () => {
30866
31256
  });
30867
31257
  if (!this.isActive(rt) || rt.state !== "connected") continue;
30868
- } catch {
31258
+ } catch (error) {
30869
31259
  if (this.isActive(rt) && rt.state === "connected") {
31260
+ console.warn(
31261
+ `[MCP] ${rt.id.tenantId}/${rt.id.serverKey}: health check failed; scheduling reconnect`,
31262
+ error instanceof Error ? error.message : error
31263
+ );
30870
31264
  rt.state = "failed";
30871
31265
  this.scheduleReconnect(rt);
30872
31266
  }
@@ -31072,7 +31466,8 @@ var McpLatticeManager = class _McpLatticeManager extends BaseLatticeManager {
31072
31466
  const visibility = Array.isArray(rawVisibility) ? rawVisibility : typeof rawVisibility === "string" ? [rawVisibility] : void 0;
31073
31467
  if (visibility && !visibility.includes("model")) continue;
31074
31468
  const ref = extractAppsRef(meta, serverKey);
31075
- const registered = ref ? wrapMcpToolWithUi(tool68, ref) : tool68;
31469
+ const resilient = this.wrapToolForResilience(rt, tool68);
31470
+ const registered = ref ? wrapMcpToolWithUi(resilient, ref) : resilient;
31076
31471
  const toolKey = _McpLatticeManager.buildToolKey(serverKey, tool68.name);
31077
31472
  try {
31078
31473
  toolLatticeManager.registerExistingToolWithTenant(tenantId2, toolKey, registered);
@@ -31141,6 +31536,7 @@ var McpLatticeManager = class _McpLatticeManager extends BaseLatticeManager {
31141
31536
  }
31142
31537
  }
31143
31538
  this.runtimes.clear();
31539
+ this.reconnectPromises.clear();
31144
31540
  }
31145
31541
  };
31146
31542
  var mcpManager = McpLatticeManager.getInstance();
@@ -34291,13 +34687,16 @@ var LatticeEvalProject = class {
34291
34687
  constructor(project, onCaseComplete) {
34292
34688
  this.suites = /* @__PURE__ */ new Map();
34293
34689
  this.project = project;
34294
- const judgeModelKey = resolveJudgeModelKey(this.project.judge_agent_config.modelKey, modelLatticeManager);
34690
+ const tenantId2 = this.project.lattice_server_config.tenant_id || "default";
34691
+ const judgeModelKey = resolveJudgeModelKey(
34692
+ this.project.judge_agent_config.modelKey,
34693
+ modelLatticeManager
34694
+ );
34295
34695
  if (!modelLatticeManager.hasLattice(judgeModelKey)) {
34296
34696
  throw new Error(
34297
34697
  `Judge model "${judgeModelKey}" not found. Register it first or provide a valid modelKey.`
34298
34698
  );
34299
34699
  }
34300
- const tenantId2 = this.project.lattice_server_config.tenant_id || "default";
34301
34700
  this.judgeAgentKey = `${this.project.projectName}_judge_agent`;
34302
34701
  const judgeAgentConfig = {
34303
34702
  key: this.judgeAgentKey,
@@ -35240,7 +35639,7 @@ function snapshotDate(value) {
35240
35639
 
35241
35640
  // src/services/TaskLifecycleService.ts
35242
35641
  import { createHash as createHash6 } from "crypto";
35243
- import { parseTaskBeliefState, replaceTaskBeliefState, snapshotExactArray as snapshotExactArray2, snapshotExactRecord as snapshotExactRecord7, taskBeliefStatesEqual } from "@axiom-lattice/protocols";
35642
+ import { parseTaskBeliefState, replaceTaskBeliefState, taskBeliefStatesEqual } from "@axiom-lattice/protocols";
35244
35643
 
35245
35644
  // src/middlewares/taskBelief.ts
35246
35645
  import { createHash as createHash5 } from "crypto";
@@ -35352,14 +35751,6 @@ async function applyBeliefImpacts(taskStore, owner, impacts) {
35352
35751
  }
35353
35752
  return { description: current.description ?? "", task: current };
35354
35753
  }
35355
- function parseExternalDependency(value) {
35356
- const row = snapshotExactRecord7(value, ["type", "summary", "requestedAt", "dependencyTaskIds"]);
35357
- const ids = snapshotExactArray2(row?.dependencyTaskIds);
35358
- if (!row || row.type !== "external_dependency" || typeof row.summary !== "string" || !row.summary.trim() || typeof row.requestedAt !== "string" || !Number.isFinite(Date.parse(row.requestedAt)) || !ids || ids.length === 0 || ids.some((id) => typeof id !== "string" || !id.trim())) return void 0;
35359
- const trimmed = ids.map((id) => id.trim());
35360
- if (new Set(trimmed).size !== trimmed.length) return void 0;
35361
- return { type: "external_dependency", summary: row.summary, requestedAt: row.requestedAt, dependencyTaskIds: trimmed };
35362
- }
35363
35754
  function unsupportedTaskStatus() {
35364
35755
  return failure2(
35365
35756
  "TASK_STATUS_UNSUPPORTED",
@@ -35658,9 +36049,6 @@ var TaskLifecycleService = class {
35658
36049
  if (interruption && typeof interruption === "object" && interruption.type === "review_required") {
35659
36050
  return failure2("REVIEW_APPROVAL_REQUIRED", "Review interruption requires explicit approval or rejection.");
35660
36051
  }
35661
- if (interruption && typeof interruption === "object" && interruption.type === "external_dependency" && !parseExternalDependency(interruption)) {
35662
- return failure2("INVALID_EXTERNAL_DEPENDENCY", "Stored external dependency interruption is malformed.");
35663
- }
35664
36052
  const previousInterruption = interruption && typeof interruption === "object" ? interruption : {};
35665
36053
  const resumeKey = this.resumeEventKey(task);
35666
36054
  const audit = { eventKey: resumeKey, previousInterruption };
@@ -35841,6 +36229,108 @@ var TaskLifecycleService = class {
35841
36229
  detail: { previousStatus: "failed" }
35842
36230
  });
35843
36231
  }
36232
+ /**
36233
+ * Reopens a terminal agent task by returning it to in_progress.
36234
+ *
36235
+ * @param input Task, actor, and optional thread.
36236
+ * @returns Structured lifecycle result. `completed` and `cancelled` tasks
36237
+ * reopen to `in_progress`; an already-`in_progress` task is acknowledged
36238
+ * without mutation; any other status or a lost CAS yields
36239
+ * `TASK_STATUS_CONFLICT`.
36240
+ * @remarks This is the single reverse edge of the terminal-irreversibility
36241
+ * relaxation. It records the status change only; re-dispatch is owned by
36242
+ * the application layer and is never triggered here.
36243
+ */
36244
+ async reopenTask(input) {
36245
+ return this.transitionAgentTask({
36246
+ ...input,
36247
+ targetStatus: "in_progress",
36248
+ expectedStatuses: ["completed", "cancelled"],
36249
+ updates: { status: "in_progress", failureReason: null, result: null },
36250
+ summary: "Task reopened",
36251
+ detail: { type: "reopen" }
36252
+ });
36253
+ }
36254
+ /**
36255
+ * Transfers a human-authored task's ownership to a chosen Agent before dispatch.
36256
+ *
36257
+ * @param input Task, actor, destination Agent, and optional Task Thread.
36258
+ * @returns Structured result with the reassigned owner and an `owner_assigned` audit.
36259
+ * @remarks Only tasks with `context.source === "user"` can be assigned this way.
36260
+ * Repeated assignment to the same owner is idempotent (repair-writes the audit
36261
+ * without a CAS). This changes owner only, never status, so it publishes no
36262
+ * Project lifecycle notification.
36263
+ */
36264
+ async assignOwner(input) {
36265
+ let task;
36266
+ try {
36267
+ task = await this.deps.taskStore.getById(input.tenantId, input.taskId);
36268
+ } catch {
36269
+ return failure2("TASK_STORE_READ_FAILED", "Failed to read task lifecycle state.");
36270
+ }
36271
+ if (!task) return failure2("TASK_NOT_FOUND", "Task was not found.");
36272
+ if (task.context?.source !== "user") {
36273
+ return failure2("TASK_NOT_HUMAN_MANAGED", "Only user-authored tasks can be assigned to an Agent.");
36274
+ }
36275
+ const storedThreadId = typeof task.context?.thread_id === "string" && task.context.thread_id.length > 0 ? task.context.thread_id : void 0;
36276
+ const effectiveThreadId = storedThreadId ?? input.threadId;
36277
+ const detail = { type: "owner_assigned", fromOwnerType: task.ownerType, toOwnerId: input.ownerId };
36278
+ if (task.ownerType === "agent" && task.ownerId === input.ownerId && (input.threadId === void 0 || storedThreadId === input.threadId)) {
36279
+ const eventKey2 = this.statusEventKey(task, "owner_assigned", detail);
36280
+ const event2 = await this.writeStatusEvent(
36281
+ task,
36282
+ input.actor,
36283
+ effectiveThreadId,
36284
+ "activity",
36285
+ "Task owner assigned",
36286
+ detail,
36287
+ eventKey2
36288
+ );
36289
+ return {
36290
+ success: true,
36291
+ task,
36292
+ mutated: false,
36293
+ eventKey: event2.eventKey,
36294
+ ...event2.warning ? { warnings: [event2.warning] } : {}
36295
+ };
36296
+ }
36297
+ const context = { ...task.context ?? {} };
36298
+ if (input.threadId && !storedThreadId) context.thread_id = input.threadId;
36299
+ let updated;
36300
+ try {
36301
+ updated = await this.deps.taskStore.updateIfStatusAndUpdatedAt(
36302
+ input.tenantId,
36303
+ input.taskId,
36304
+ { ownerType: "agent", ownerId: input.ownerId, context },
36305
+ [task.status],
36306
+ task.updatedAt
36307
+ );
36308
+ } catch {
36309
+ return failure2(
36310
+ "TASK_STORE_WRITE_FAILED",
36311
+ "Failed to assign the task owner.",
36312
+ "The store rejected the write."
36313
+ );
36314
+ }
36315
+ if (!updated) return failure2("TASK_STATUS_CONFLICT", "Task changed concurrently.");
36316
+ const eventKey = this.statusEventKey(updated, "owner_assigned", detail);
36317
+ const event = await this.writeStatusEvent(
36318
+ updated,
36319
+ input.actor,
36320
+ effectiveThreadId,
36321
+ "activity",
36322
+ "Task owner assigned",
36323
+ detail,
36324
+ eventKey
36325
+ );
36326
+ return {
36327
+ success: true,
36328
+ task: updated,
36329
+ mutated: true,
36330
+ eventKey: event.eventKey,
36331
+ ...event.warning ? { warnings: [event.warning] } : {}
36332
+ };
36333
+ }
35844
36334
  /** Atomically cancels a nonterminal agent task and records an idempotent status event. */
35845
36335
  async cancelTask(input) {
35846
36336
  return this.transitionAgentTask({
@@ -35921,19 +36411,7 @@ var TaskLifecycleService = class {
35921
36411
  async interruptTask(input) {
35922
36412
  const summary = input.summary.trim();
35923
36413
  if (!summary) return failure2("MISSING_INTERRUPTION_SUMMARY", "Agent interruption requires a nonblank summary.");
35924
- if (input.type !== "external_dependency" && input.dependencyTaskIds !== void 0) {
35925
- return failure2("INVALID_EXTERNAL_DEPENDENCY", "dependencyTaskIds are only valid for external dependencies.");
35926
- }
35927
36414
  const requestedAt = this.now().toISOString();
35928
- const external = input.type === "external_dependency" ? parseExternalDependency({
35929
- type: input.type,
35930
- summary,
35931
- requestedAt,
35932
- dependencyTaskIds: input.dependencyTaskIds
35933
- }) : void 0;
35934
- if (input.type === "external_dependency" && !external) {
35935
- return failure2("INVALID_EXTERNAL_DEPENDENCY", "External dependencies require exact, unique nonblank task IDs.");
35936
- }
35937
36415
  let task;
35938
36416
  try {
35939
36417
  task = await this.deps.taskStore.getById(input.tenantId, input.taskId);
@@ -35947,29 +36425,17 @@ var TaskLifecycleService = class {
35947
36425
  if (threadFailure) return threadFailure;
35948
36426
  const currentInterruption = task.context?.interruption;
35949
36427
  if (task.status === "interrupted" && currentInterruption && typeof currentInterruption === "object") {
35950
- const storedExternal = parseExternalDependency(currentInterruption);
35951
- if (input.type === "external_dependency") {
35952
- if (!external || !storedExternal || storedExternal.summary !== external.summary || canonicalJson(storedExternal.dependencyTaskIds) !== canonicalJson(external.dependencyTaskIds)) {
35953
- return failure2(
35954
- "TASK_STATUS_CONFLICT",
35955
- "Task is already interrupted by a different or malformed external dependency.",
35956
- "Use manage_task get to inspect the existing interruption in task context.interruption."
35957
- );
35958
- }
35959
- } else if (currentInterruption.type !== input.type || currentInterruption.summary !== summary) {
36428
+ if (currentInterruption.type !== input.type || currentInterruption.summary !== summary) {
35960
36429
  return failure2(
35961
36430
  "TASK_STATUS_CONFLICT",
35962
36431
  "Task is already interrupted by a different request.",
35963
36432
  "Use manage_task get to inspect the existing interruption, or resume it first."
35964
36433
  );
35965
36434
  }
35966
- const detail = storedExternal ? { ...storedExternal } : currentInterruption;
36435
+ const detail = currentInterruption;
35967
36436
  const eventKey2 = this.statusEventKey(task, "interrupted", detail);
35968
36437
  const event2 = await this.writeStatusEvent(task, input.actor, input.threadId, "interrupted", summary, detail, eventKey2);
35969
36438
  const warnings2 = event2.warning ? [event2.warning] : [];
35970
- if (storedExternal && task.workspaceId && task.projectId) {
35971
- return this.finishExternalDependencyInterruption(task, input, storedExternal, event2, warnings2, false);
35972
- }
35973
36439
  return {
35974
36440
  success: true,
35975
36441
  task,
@@ -35985,11 +36451,7 @@ var TaskLifecycleService = class {
35985
36451
  "Only pending or in_progress tasks can be interrupted. Use manage_task get to check the current status."
35986
36452
  );
35987
36453
  }
35988
- if (external && task.workspaceId && task.projectId) {
35989
- const dependencyFailure = await this.validateExternalDependencies(task, external.dependencyTaskIds);
35990
- if (dependencyFailure) return dependencyFailure;
35991
- }
35992
- const interruption = external ? { ...external } : { type: input.type, summary, requestedAt };
36454
+ const interruption = { type: input.type, summary, requestedAt };
35993
36455
  let updated;
35994
36456
  try {
35995
36457
  updated = await this.deps.taskStore.updateIfStatusAndUpdatedAt(input.tenantId, input.taskId, {
@@ -36011,9 +36473,6 @@ var TaskLifecycleService = class {
36011
36473
  const eventKey = this.statusEventKey(updated, "interrupted", interruption);
36012
36474
  const event = await this.writeStatusEvent(updated, input.actor, input.threadId, "interrupted", summary, interruption, eventKey);
36013
36475
  const warnings = event.warning ? [event.warning] : await this.notifyWinningTransition(updated, "interrupted", event);
36014
- if (external && updated.workspaceId && updated.projectId) {
36015
- return this.finishExternalDependencyInterruption(updated, input, external, event, warnings, true);
36016
- }
36017
36476
  return {
36018
36477
  success: true,
36019
36478
  task: updated,
@@ -37252,7 +37711,9 @@ var TaskLifecycleService = class {
37252
37711
  };
37253
37712
  }
37254
37713
  if (!input.expectedStatuses.includes(task.status)) return failure2("TASK_STATUS_CONFLICT", "Task status cannot make this transition.");
37255
- const updates = { ...input.updates, context: clearInterruptionContext(task.context) };
37714
+ const clearedContext = clearInterruptionContext(task.context);
37715
+ const withThread = input.threadId && task.context?.thread_id === void 0 ? { ...clearedContext ?? {}, thread_id: input.threadId } : clearedContext;
37716
+ const updates = { ...input.updates, context: withThread };
37256
37717
  let updated;
37257
37718
  try {
37258
37719
  updated = await this.deps.taskStore.updateIfStatusAndUpdatedAt(
@@ -37340,85 +37801,6 @@ var TaskLifecycleService = class {
37340
37801
  return [{ code: "LIFECYCLE_NOTIFICATION_FAILED", taskId: task.id, eventKey: event.eventKey }];
37341
37802
  }
37342
37803
  }
37343
- async validateExternalDependencies(task, dependencyTaskIds) {
37344
- const declared = new Set(task.dependencies ?? []);
37345
- if (dependencyTaskIds.some((id) => id === task.id || !declared.has(id))) {
37346
- return failure2("INVALID_EXTERNAL_DEPENDENCY", "External dependency IDs must be declared non-self dependencies.");
37347
- }
37348
- try {
37349
- for (const id of dependencyTaskIds) {
37350
- const dependency = await this.deps.taskStore.getById(task.tenantId, id);
37351
- if (!dependency || dependency.workspaceId !== task.workspaceId || dependency.projectId !== task.projectId || dependency.status === "completed") {
37352
- return failure2("INVALID_EXTERNAL_DEPENDENCY", "External dependencies must exist, remain incomplete, and share exact scope.");
37353
- }
37354
- }
37355
- } catch {
37356
- return failure2("TASK_STORE_READ_FAILED", "Failed to validate external dependencies.");
37357
- }
37358
- return void 0;
37359
- }
37360
- async externalDependencyReadiness(task, dependencyTaskIds) {
37361
- try {
37362
- for (const id of dependencyTaskIds) {
37363
- const dependency = await this.deps.taskStore.getById(task.tenantId, id);
37364
- if (!dependency || dependency.workspaceId !== task.workspaceId || dependency.projectId !== task.projectId || dependency.status !== "completed") return { kind: "blocked" };
37365
- }
37366
- return { kind: "ready" };
37367
- } catch {
37368
- return {
37369
- kind: "error",
37370
- warning: { code: "DEPENDENCY_READ_FAILED", taskId: task.id }
37371
- };
37372
- }
37373
- }
37374
- async finishExternalDependencyInterruption(task, input, interruption, event, warnings, mutated) {
37375
- if (event.warning) {
37376
- return { success: true, task, mutated, eventKey: event.eventKey, warnings };
37377
- }
37378
- const readiness = await this.externalDependencyReadiness(task, interruption.dependencyTaskIds);
37379
- if (readiness.kind === "blocked") {
37380
- return { success: true, task, mutated, eventKey: event.eventKey, ...warnings.length ? { warnings } : {} };
37381
- }
37382
- if (readiness.kind === "error") {
37383
- return {
37384
- success: true,
37385
- task,
37386
- mutated,
37387
- eventKey: event.eventKey,
37388
- warnings: [...warnings, { ...readiness.warning, eventKey: event.eventKey }]
37389
- };
37390
- }
37391
- const resumed = await this.resumeInterruption({
37392
- tenantId: input.tenantId,
37393
- taskId: input.taskId,
37394
- actor: input.actor,
37395
- threadId: input.threadId
37396
- });
37397
- if (resumed.success) {
37398
- const combined = [...warnings, ...resumed.warnings ?? []];
37399
- return {
37400
- ...resumed,
37401
- mutated: mutated || resumed.mutated,
37402
- ...combined.length ? { warnings: combined } : {}
37403
- };
37404
- }
37405
- let current;
37406
- try {
37407
- current = await this.deps.taskStore.getById(input.tenantId, input.taskId);
37408
- } catch {
37409
- return resumed;
37410
- }
37411
- if (current?.status === "in_progress" && (!current.context?.thread_id || current.context?.thread_id === input.threadId)) {
37412
- return {
37413
- success: true,
37414
- task: current,
37415
- mutated,
37416
- eventKey: event.eventKey,
37417
- ...warnings.length ? { warnings } : {}
37418
- };
37419
- }
37420
- return resumed;
37421
- }
37422
37804
  async clearAuditMarker(task, key5) {
37423
37805
  const context = withoutContextKey(task.context, key5);
37424
37806
  try {
@@ -38664,6 +39046,47 @@ function clearEncryptionKeyCache() {
38664
39046
  keyValidated = false;
38665
39047
  }
38666
39048
 
39049
+ // src/util/safeUpstreamUrl.ts
39050
+ var UnsafeUpstreamUrlError = class extends Error {
39051
+ constructor(reason, message, statusCode = 400) {
39052
+ super(message);
39053
+ this.reason = reason;
39054
+ this.statusCode = statusCode;
39055
+ this.name = "UnsafeUpstreamUrlError";
39056
+ }
39057
+ };
39058
+ async function assertSafeUpstreamUrl(raw) {
39059
+ let url;
39060
+ try {
39061
+ url = new URL(raw);
39062
+ } catch {
39063
+ throw new UnsafeUpstreamUrlError(
39064
+ "INVALID_BASE_URL",
39065
+ "Base URL is not a valid URL"
39066
+ );
39067
+ }
39068
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
39069
+ throw new UnsafeUpstreamUrlError(
39070
+ "UNSUPPORTED_SCHEME",
39071
+ "Base URL must use http or https"
39072
+ );
39073
+ }
39074
+ if (url.username.length > 0 || url.password.length > 0) {
39075
+ throw new UnsafeUpstreamUrlError(
39076
+ "CREDENTIALS_IN_URL",
39077
+ "Base URL must not embed credentials; configure the API key separately"
39078
+ );
39079
+ }
39080
+ const hostname = url.hostname.replace(/^\[/, "").replace(/\]$/, "");
39081
+ if (hostname.length === 0) {
39082
+ throw new UnsafeUpstreamUrlError(
39083
+ "INVALID_BASE_URL",
39084
+ "Base URL must include a host"
39085
+ );
39086
+ }
39087
+ return { url };
39088
+ }
39089
+
38667
39090
  // src/middlewares/skillMiddleware.ts
38668
39091
  import { createMiddleware as createMiddleware19 } from "langchain";
38669
39092
 
@@ -41316,6 +41739,9 @@ function trustedRosterScope(trusted) {
41316
41739
  callerAssistantId: context.assistantId
41317
41740
  };
41318
41741
  }
41742
+ function trustedRosterExempt(trusted) {
41743
+ return trusted.projectTask?.rosterExempt === true;
41744
+ }
41319
41745
  async function requireTrustedOwner(trusted, targetAssistantId) {
41320
41746
  const context = trusted.projectTask ?? trusted.projectRoom;
41321
41747
  const memberships = getStoreLattice("default", "projectBotMembership").store;
@@ -41580,6 +42006,7 @@ async function validateTrustedChildDependencyWait(store, tenantId2, parent, depe
41580
42006
  if (dependency.status === "completed") continue;
41581
42007
  if (!["pending", "in_progress", "interrupted"].includes(dependency.status) || dependency.parentId !== parent.id || dependency.ownerType !== "agent" || !hasExactContextSource(dependency, expectedSource)) return void 0;
41582
42008
  incompleteChildren += 1;
42009
+ if (trustedRosterExempt(trusted)) continue;
41583
42010
  try {
41584
42011
  await requireTrustedOwner(trusted, dependency.ownerId);
41585
42012
  } catch (error) {
@@ -41654,7 +42081,6 @@ var manageTaskSchema = z70.object({
41654
42081
  ownerId: z70.string().optional().describe("Owner ID. Auto-filled from current user/agent if omitted"),
41655
42082
  requireReview: z70.boolean().optional().describe("If true, agent completion pauses as interrupted(review_required) for HITL approval"),
41656
42083
  dependencies: z70.array(z70.string()).optional().describe("List of task IDs that must be completed before this task can start"),
41657
- dependencyTaskIds: z70.array(z70.string().trim().min(1)).optional().describe("Exact blocking task IDs required for an external_dependency interruption"),
41658
42084
  result: z70.string().optional().describe("Result summary. Used with set_status status='completed' or update status='completed'"),
41659
42085
  failureReason: z70.string().optional().describe("Failure reason. Used with set_status status='failed' or update status='failed'"),
41660
42086
  files: z70.array(z70.object({
@@ -41823,7 +42249,7 @@ function createTaskMiddleware(options = {}) {
41823
42249
  const workspaceId = trustedScope?.workspaceId ?? rc.workspaceId;
41824
42250
  const projectId = trustedScope?.projectId ?? rc.projectId;
41825
42251
  const revalidateCaller = async () => {
41826
- if (!trustedProject) return void 0;
42252
+ if (!trustedProject || trustedRosterExempt(trustedProject)) return void 0;
41827
42253
  try {
41828
42254
  await createProjectTaskOwnerPolicy().requireCaller(trustedRosterScope(trustedProject));
41829
42255
  } catch (error) {
@@ -41831,7 +42257,7 @@ function createTaskMiddleware(options = {}) {
41831
42257
  }
41832
42258
  return void 0;
41833
42259
  };
41834
- if (trustedProject) {
42260
+ if (trustedProject && !trustedRosterExempt(trustedProject)) {
41835
42261
  try {
41836
42262
  await createProjectTaskOwnerPolicy().requireCaller(trustedRosterScope(trustedProject));
41837
42263
  } catch (error) {
@@ -41864,10 +42290,12 @@ function createTaskMiddleware(options = {}) {
41864
42290
  if (trustedProject) {
41865
42291
  const snapshot = taskMutationSnapshot(existing);
41866
42292
  if (!snapshot) return trustedContextError();
41867
- try {
41868
- await createProjectTaskOwnerPolicy().requireCaller(trustedRosterScope(trustedProject));
41869
- } catch (error) {
41870
- return projectPolicyError(error);
42293
+ if (!trustedRosterExempt(trustedProject)) {
42294
+ try {
42295
+ await createProjectTaskOwnerPolicy().requireCaller(trustedRosterScope(trustedProject));
42296
+ } catch (error) {
42297
+ return projectPolicyError(error);
42298
+ }
41871
42299
  }
41872
42300
  deleted = await store.deleteIfSnapshot(tenantId2, id, snapshot);
41873
42301
  } else {
@@ -41920,13 +42348,15 @@ function createTaskMiddleware(options = {}) {
41920
42348
  "Target Project task owner is not active in the exact roster."
41921
42349
  ));
41922
42350
  }
41923
- try {
41924
- await requireTrustedOwner(trustedProject, ownerId);
41925
- } catch (error) {
41926
- return projectPolicyError(error instanceof ProjectTaskOwnerPolicyError ? error : new ProjectTaskOwnerPolicyError(
41927
- "PROJECT_TASK_OWNER_INACTIVE",
41928
- "Target Project task owner is not active in the exact roster."
41929
- ));
42351
+ if (!trustedRosterExempt(trustedProject)) {
42352
+ try {
42353
+ await requireTrustedOwner(trustedProject, ownerId);
42354
+ } catch (error) {
42355
+ return projectPolicyError(error instanceof ProjectTaskOwnerPolicyError ? error : new ProjectTaskOwnerPolicyError(
42356
+ "PROJECT_TASK_OWNER_INACTIVE",
42357
+ "Target Project task owner is not active in the exact roster."
42358
+ ));
42359
+ }
41930
42360
  }
41931
42361
  }
41932
42362
  if (effectiveOwnerType === "agent" && (input.result !== void 0 || input.failureReason !== void 0)) {
@@ -41974,10 +42404,12 @@ function createTaskMiddleware(options = {}) {
41974
42404
  )) return trustedContextError("Parent or dependency task changed before creation.");
41975
42405
  const callerError = await revalidateCaller();
41976
42406
  if (callerError) return callerError;
41977
- try {
41978
- await requireTrustedOwner(trustedProject, ownerId);
41979
- } catch (error) {
41980
- return projectPolicyError(error);
42407
+ if (!trustedRosterExempt(trustedProject)) {
42408
+ try {
42409
+ await requireTrustedOwner(trustedProject, ownerId);
42410
+ } catch (error) {
42411
+ return projectPolicyError(error);
42412
+ }
41981
42413
  }
41982
42414
  }
41983
42415
  return store.create({
@@ -42235,18 +42667,6 @@ function createTaskMiddleware(options = {}) {
42235
42667
  return trustedFailure;
42236
42668
  }
42237
42669
  }
42238
- if (trustedProject && input.dependencyTaskIds !== void 0) {
42239
- const interruption = input.context?.interruption;
42240
- const interruptionType2 = interruption && typeof interruption === "object" ? interruption.type : void 0;
42241
- if (input.status !== "interrupted" || interruptionType2 !== "external_dependency") {
42242
- taskUpdateBlocked(input.id, "INVALID_EXTERNAL_DEPENDENCY", {});
42243
- return JSON.stringify({
42244
- success: false,
42245
- code: "INVALID_EXTERNAL_DEPENDENCY",
42246
- error: "dependencyTaskIds are only valid for external dependency interruptions."
42247
- });
42248
- }
42249
- }
42250
42670
  if (trustedProject && (input.ownerType !== void 0 && input.ownerType !== existing.ownerType || input.ownerId !== void 0 && input.ownerId !== existing.ownerId)) {
42251
42671
  taskUpdateBlocked(input.id, "TASK_OWNERSHIP_VIA_DISPATCH_REQUIRED", {
42252
42672
  inputOwnerType: input.ownerType,
@@ -42438,7 +42858,7 @@ function createTaskMiddleware(options = {}) {
42438
42858
  return trustedContextError("Parent or dependency task changed before update.");
42439
42859
  }
42440
42860
  }
42441
- if (trustedProject) {
42861
+ if (trustedProject && !trustedRosterExempt(trustedProject)) {
42442
42862
  try {
42443
42863
  await createProjectTaskOwnerPolicy().requireCaller(trustedRosterScope(trustedProject));
42444
42864
  } catch (error) {
@@ -42478,7 +42898,7 @@ function createTaskMiddleware(options = {}) {
42478
42898
  const workItemSummary = input.summary;
42479
42899
  let activityWarnings;
42480
42900
  try {
42481
- if (trustedProject) {
42901
+ if (trustedProject && !trustedRosterExempt(trustedProject)) {
42482
42902
  try {
42483
42903
  await createProjectTaskOwnerPolicy().requireCaller(trustedRosterScope(trustedProject));
42484
42904
  } catch (error) {
@@ -42563,18 +42983,6 @@ function createTaskMiddleware(options = {}) {
42563
42983
  return trustedFailure;
42564
42984
  }
42565
42985
  }
42566
- if (trustedProject && input.dependencyTaskIds !== void 0) {
42567
- const interruption = input.context?.interruption;
42568
- const interruptionType2 = interruption && typeof interruption === "object" ? interruption.type : void 0;
42569
- if (input.status !== "interrupted" || interruptionType2 !== "external_dependency") {
42570
- taskUpdateBlocked(input.id, "INVALID_EXTERNAL_DEPENDENCY", {});
42571
- return JSON.stringify({
42572
- success: false,
42573
- code: "INVALID_EXTERNAL_DEPENDENCY",
42574
- error: "dependencyTaskIds are only valid for external dependency interruptions."
42575
- });
42576
- }
42577
- }
42578
42986
  if (trustedProject && (input.ownerType !== void 0 && input.ownerType !== existing.ownerType || input.ownerId !== void 0 && input.ownerId !== existing.ownerId)) {
42579
42987
  taskUpdateBlocked(input.id, "TASK_OWNERSHIP_VIA_DISPATCH_REQUIRED", {});
42580
42988
  return JSON.stringify({
@@ -42656,11 +43064,11 @@ function createTaskMiddleware(options = {}) {
42656
43064
  const interruption = input.context?.interruption;
42657
43065
  const explicitType = interruption && typeof interruption === "object" ? interruption.type : void 0;
42658
43066
  const type = explicitType ?? "missing_input";
42659
- if (type !== "missing_input" && type !== "external_dependency" && type !== "user_decision") {
43067
+ if (type !== "missing_input" && type !== "user_decision") {
42660
43068
  return JSON.stringify({
42661
43069
  success: false,
42662
43070
  code: "INVALID_INTERRUPTION_TYPE",
42663
- error: "Agent interruption type must be missing_input, external_dependency, or user_decision."
43071
+ error: "Agent interruption type must be missing_input or user_decision."
42664
43072
  });
42665
43073
  }
42666
43074
  await validateExecutionResult();
@@ -42672,8 +43080,7 @@ function createTaskMiddleware(options = {}) {
42672
43080
  type,
42673
43081
  summary: input.summary ?? "",
42674
43082
  actor,
42675
- threadId: statusThreadId,
42676
- dependencyTaskIds: input.dependencyTaskIds
43083
+ threadId: statusThreadId
42677
43084
  });
42678
43085
  return lifecycleMutationResponse(interrupted);
42679
43086
  }
@@ -43381,7 +43788,6 @@ canonical Belief State:
43381
43788
  };
43382
43789
 
43383
43790
  // src/middlewares/documentLearningMiddleware.ts
43384
- import { AgentType as AgentType9 } from "@axiom-lattice/protocols";
43385
43791
  import { createMiddleware as createMiddleware24 } from "langchain";
43386
43792
 
43387
43793
  // src/middlewares/documentLearningSkills.ts
@@ -43626,15 +44032,6 @@ var createDocumentLearningMiddleware = () => createMiddleware24({
43626
44032
  contextSchema,
43627
44033
  tools: []
43628
44034
  });
43629
- var DOCUMENT_LEARNER_SYSTEM_PROMPT = `You are a capability learning specialist.
43630
-
43631
- CRITICAL FIRST ACTION \u2014 before any response about the task:
43632
- 1. Call the \`skill\` tool with skill_name: "agent-architecture" to load the
43633
- authoritative Charter.
43634
- 2. Call the \`skill\` tool with skill_name: "document-learning-learn-capability" to
43635
- load the source-to-capability workflow.
43636
- Never announce that you will follow a skill. Load both in this order and follow
43637
- their content. If either load fails, retry it once, then report the failure.`;
43638
44035
  var documentLearningPlugin = {
43639
44036
  meta: {
43640
44037
  type: "document-learning",
@@ -43643,92 +44040,6 @@ var documentLearningPlugin = {
43643
44040
  description: "\u4ECE\u6587\u6863\u4E2D\u5B66\u4E60\u77E5\u8BC6\uFF0C\u81EA\u52A8\u6784\u5EFA\u6280\u80FD\u4F53\u7CFB\u548C\u8BC4\u6D4B\u3002 Learn knowledge from documents and build structured skill systems with evaluations.",
43644
44041
  recommends: ["skill", "eval"]
43645
44042
  },
43646
- agents: {
43647
- "document-learner": {
43648
- key: "document-learner",
43649
- name: "Document Learning Agent",
43650
- description: "Read documents and build structured skill systems with evaluations. Use this agent when you need to extract knowledge from documents and turn it into reusable, testable agent skills.",
43651
- type: AgentType9.DEEP_AGENT,
43652
- prompt: DOCUMENT_LEARNER_SYSTEM_PROMPT,
43653
- subAgents: ["document-parser-benchmark"],
43654
- tools: [
43655
- "invoke_agent",
43656
- "list_agents",
43657
- "create_agent",
43658
- "update_agent",
43659
- "delete_agent",
43660
- "get_agent",
43661
- "list_tools",
43662
- "list_middleware_types"
43663
- ],
43664
- middleware: [
43665
- {
43666
- id: "skill",
43667
- type: "skill",
43668
- name: "Skill",
43669
- description: "Skill creation and loading",
43670
- enabled: true,
43671
- config: { readAll: true }
43672
- },
43673
- {
43674
- id: "document-learning",
43675
- type: "document-learning",
43676
- name: "Document Learning",
43677
- description: "Source-to-capability learning workflow",
43678
- enabled: true,
43679
- config: {}
43680
- },
43681
- {
43682
- id: "eval",
43683
- type: "eval",
43684
- name: "Eval",
43685
- description: "Evaluation design and execution",
43686
- enabled: true,
43687
- config: {}
43688
- },
43689
- {
43690
- id: "task",
43691
- type: "task",
43692
- name: "Task",
43693
- description: "Training process tracking with approval gates",
43694
- enabled: true,
43695
- config: {}
43696
- },
43697
- {
43698
- id: "widget",
43699
- type: "widget",
43700
- name: "Widget",
43701
- description: "Visualize benchmark results and eval dashboards",
43702
- enabled: true,
43703
- config: {}
43704
- },
43705
- {
43706
- id: "ask_user_to_clarify",
43707
- type: "ask_user_to_clarify",
43708
- name: "Ask User",
43709
- description: "Wait for user input at approval gates",
43710
- enabled: true,
43711
- config: {}
43712
- },
43713
- {
43714
- id: "filesystem",
43715
- type: "filesystem",
43716
- name: "Filesystem",
43717
- description: "Read documents, write skill files",
43718
- enabled: true,
43719
- config: {}
43720
- },
43721
- {
43722
- id: "document-parser",
43723
- type: "document-parser",
43724
- name: "Document Parser",
43725
- description: "Parse documents with the chosen engine",
43726
- enabled: true,
43727
- config: { connectAll: true }
43728
- }
43729
- ]
43730
- }
43731
- },
43732
44043
  skills: {
43733
44044
  "document-learning-learn-capability": {
43734
44045
  version: "1.0.0",
@@ -43957,7 +44268,7 @@ function createParseDocumentTool({
43957
44268
  }
43958
44269
 
43959
44270
  // src/middlewares/documentParserMiddleware.ts
43960
- import { AgentType as AgentType10 } from "@axiom-lattice/protocols";
44271
+ import { AgentType as AgentType9 } from "@axiom-lattice/protocols";
43961
44272
 
43962
44273
  // src/middlewares/documentBenchmarkWorkflow.ts
43963
44274
  var DOCUMENT_PARSER_BENCHMARK_YAML = `name: document-parser-benchmark
@@ -44410,6 +44721,23 @@ function createDocumentParserMiddleware(config) {
44410
44721
  tools: [createParseDocumentTool({ connectAll, connections, baseUrl, apiKey })]
44411
44722
  });
44412
44723
  }
44724
+ var DOCUMENT_PARSER_SYSTEM_PROMPT = `You are the Document Parser. Turn documents (pdf, docx, images, spreadsheets, and similar) into clean Markdown.
44725
+
44726
+ CRITICAL FIRST ACTION: call the \`skill\` tool with skill_name: "document-parser-markitdown" and follow it. Never announce skill loads.
44727
+
44728
+ DEFAULT \u2014 local MarkItDown:
44729
+ - Single file: run \`markitdown <input> -o <output.md>\` via shell.
44730
+ - Batch or literature collections: use the scripts bundled in the skill.
44731
+ - After converting, verify the output is non-empty and its structure/tables are intact, then report the exact input -> output paths.
44732
+
44733
+ ESCALATION \u2014 remote parse_document:
44734
+ - Call parse_document only when local conversion fails, the format is unsupported, the output is empty or materially incomplete, the structure is badly damaged, OCR quality is inadequate, or the user explicitly asks for professional parsing.
44735
+ - When the user is unsatisfied with quality, use ask_user_to_clarify to ask whether to switch to a remote engine, and recommend one engine based on the observed file features and the user's feedback. If the user names an engine, use it.
44736
+
44737
+ BENCHMARK:
44738
+ - Only when the user explicitly asks to compare engines. Confirm with ask_user_to_clarify first, then delegate to the document-parser-benchmark subagent. Never benchmark by default.
44739
+
44740
+ Report only what you actually produced; never claim success without inspecting the output file.`;
44413
44741
  var documentParserPlugin = {
44414
44742
  meta: {
44415
44743
  type: "document-parser",
@@ -44480,9 +44808,59 @@ var documentParserPlugin = {
44480
44808
  },
44481
44809
  middleware: (config) => createDocumentParserMiddleware(config),
44482
44810
  agents: {
44811
+ "document-parser": {
44812
+ key: "document-parser",
44813
+ type: AgentType9.DEEP_AGENT,
44814
+ name: "Document Parser",
44815
+ description: "Parse documents (pdf, docx, images, spreadsheets) into clean Markdown. Defaults to local MarkItDown; escalates to remote engines only when quality or format needs it; runs engine benchmarking only on explicit request.",
44816
+ prompt: DOCUMENT_PARSER_SYSTEM_PROMPT,
44817
+ subAgents: ["document-parser-benchmark"],
44818
+ middleware: [
44819
+ {
44820
+ id: "skill",
44821
+ type: "skill",
44822
+ name: "Skill",
44823
+ description: "Load the local MarkItDown parsing procedure",
44824
+ enabled: true,
44825
+ config: { readAll: true }
44826
+ },
44827
+ {
44828
+ id: "filesystem",
44829
+ type: "filesystem",
44830
+ name: "Filesystem",
44831
+ description: "Read source documents and write Markdown output",
44832
+ enabled: true,
44833
+ config: { vmIsolation: "project" }
44834
+ },
44835
+ {
44836
+ id: "code_eval",
44837
+ type: "code_eval",
44838
+ name: "Code Runner",
44839
+ description: "Run the markitdown CLI and bundled conversion scripts",
44840
+ enabled: true,
44841
+ config: { vmIsolation: "project" }
44842
+ },
44843
+ {
44844
+ id: "document-parser",
44845
+ type: "document-parser",
44846
+ name: "Document Parser",
44847
+ description: "Remote parse_document fallback for hard documents",
44848
+ enabled: true,
44849
+ config: { connectAll: true }
44850
+ },
44851
+ {
44852
+ id: "ask_user_to_clarify",
44853
+ type: "ask_user_to_clarify",
44854
+ name: "Ask User",
44855
+ description: "Confirm engine switches and benchmark runs",
44856
+ enabled: true,
44857
+ config: {}
44858
+ }
44859
+ ]
44860
+ },
44483
44861
  "document-parser-benchmark": {
44484
44862
  key: "document-parser-benchmark",
44485
- type: AgentType10.WORKFLOW,
44863
+ type: AgentType9.WORKFLOW,
44486
44864
  name: "Document Parser Benchmark",
44487
44865
  description: "Benchmark document parsing engines. Parses a document with 5 engines in parallel, scores results, and produces a ranking report. Use this when you need to evaluate which parsing engine works best for a given document.",
44488
44866
  prompt: "Benchmark document parsing engines: parse with 5 engines, score, and rank results.",
@@ -44522,8 +44900,8 @@ import { createHash as createHash7 } from "crypto";
44522
44900
  import {
44523
44901
  descriptorDataValue as descriptorDataValue2,
44524
44902
  parseTrustedRunContext as parseTrustedRunContext3,
44525
- snapshotExactArray as snapshotExactArray3,
44526
- snapshotExactRecord as snapshotExactRecord8
44903
+ snapshotExactArray as snapshotExactArray2,
44904
+ snapshotExactRecord as snapshotExactRecord7
44527
44905
  } from "@axiom-lattice/protocols";
44528
44906
  import { createMiddleware as createMiddleware26, tool as tool66 } from "langchain";
44529
44907
  import { z as z72 } from "zod";
@@ -44571,7 +44949,7 @@ function readTrustedRoomScope(config) {
44571
44949
  }
44572
44950
  function exactValues2(value, required, optional = []) {
44573
44951
  try {
44574
- return snapshotExactRecord8(value, required, optional);
44952
+ return snapshotExactRecord7(value, required, optional);
44575
44953
  } catch {
44576
44954
  return void 0;
44577
44955
  }
@@ -44607,7 +44985,7 @@ function snapshotMembership3(value) {
44607
44985
  return { ...values, joinedAt, updatedAt };
44608
44986
  }
44609
44987
  function snapshotRows(value) {
44610
- return snapshotExactArray3(value) ?? [];
44988
+ return snapshotExactArray2(value) ?? [];
44611
44989
  }
44612
44990
  async function hasActiveCurrentMembership(scope) {
44613
44991
  const memberships = getStoreLattice("default", "projectBotMembership").store;
@@ -44694,7 +45072,7 @@ var projectRoomPlugin = {
44694
45072
  // src/middlewares/sandboxPluginsMiddleware.ts
44695
45073
  import { createMiddleware as createMiddleware27, tool as tool67 } from "langchain";
44696
45074
  import { z as z73 } from "zod";
44697
- import { AgentType as AgentType11 } from "@axiom-lattice/protocols";
45075
+ import { AgentType as AgentType10 } from "@axiom-lattice/protocols";
44698
45076
 
44699
45077
  // src/middlewares/sandboxPluginsSkills.ts
44700
45078
  var PLUGIN_JSON_TEMPLATE = `{
@@ -45444,7 +45822,7 @@ var sandboxPluginsPlugin = {
45444
45822
  key: "plugin-builder",
45445
45823
  name: "Plugin Builder",
45446
45824
  description: "Author and manage tenant plugins in the shared sandbox: clarify goal, inputs/outputs, credentials, and whether a UI is needed; then write the manifest and JS handlers, add an optional HTML MCP App UI, and validate/test with the manage_plugin tool.",
45447
- type: AgentType11.DEEP_AGENT,
45825
+ type: AgentType10.DEEP_AGENT,
45448
45826
  prompt: PLUGIN_BUILDER_PROMPT,
45449
45827
  middleware: [
45450
45828
  {
@@ -46124,6 +46502,7 @@ export {
46124
46502
  InMemoryDatabaseConfigStore,
46125
46503
  InMemoryMailboxStore,
46126
46504
  InMemoryMenuStore,
46505
+ InMemoryModelProviderStore,
46127
46506
  InMemoryOpenAuditStore,
46128
46507
  InMemoryProjectBotMembershipStore,
46129
46508
  InMemoryProjectMembershipStore,
@@ -46217,6 +46596,7 @@ export {
46217
46596
  ThreadStatus2 as ThreadStatus,
46218
46597
  TokenCache,
46219
46598
  ToolLatticeManager,
46599
+ UnsafeUpstreamUrlError,
46220
46600
  VectorStoreLatticeManager,
46221
46601
  VectorStoreProviderManager,
46222
46602
  VolumeFilesystem,
@@ -46225,6 +46605,7 @@ export {
46225
46605
  agentLatticeManager,
46226
46606
  appendReconciliationWarning,
46227
46607
  appendUiFence,
46608
+ assertSafeUpstreamUrl,
46228
46609
  buildGrepResultsDict,
46229
46610
  buildInput,
46230
46611
  buildNamedVolumeName,
@@ -46356,6 +46737,7 @@ export {
46356
46737
  invokeWithRetry,
46357
46738
  isBuiltInSkill,
46358
46739
  isDirectRoom,
46740
+ isMcpConnectionError,
46359
46741
  isPluginSkillAuthorizedInSandbox,
46360
46742
  isPluginSkillManagedInSandbox,
46361
46743
  isUsingDefaultKey,
@@ -46378,7 +46760,6 @@ export {
46378
46760
  parallelLimit,
46379
46761
  parseCronExpression,
46380
46762
  parseDirectRoomType,
46381
- parseExternalDependency,
46382
46763
  parseJudgeVerdict,
46383
46764
  parsePackSize,
46384
46765
  parseQueuedExecutionMode,
@@ -46407,6 +46788,7 @@ export {
46407
46788
  registerExistingToolWithTenant,
46408
46789
  registerLoggerLattice,
46409
46790
  registerModelLattice,
46791
+ registerModelLatticeForTenant,
46410
46792
  registerQueueLattice,
46411
46793
  registerSTTModelLattice,
46412
46794
  registerSandboxProviderType,