veryfront 0.1.1069 → 0.1.1072
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/esm/_dnt.polyfills.d.ts +13 -13
- package/esm/_dnt.polyfills.d.ts.map +1 -1
- package/esm/_dnt.polyfills.js +11 -11
- package/esm/deno.js +2 -2
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +26 -3
- package/esm/src/agent/runtime/tool-result-continuation.d.ts.map +1 -1
- package/esm/src/agent/runtime/tool-result-continuation.js +10 -2
- package/esm/src/cache/cache-key-builder.d.ts +30 -0
- package/esm/src/cache/cache-key-builder.d.ts.map +1 -1
- package/esm/src/cache/cache-key-builder.js +65 -0
- package/esm/src/cache/keys/builders/render.d.ts +1 -1
- package/esm/src/cache/keys/builders/render.d.ts.map +1 -1
- package/esm/src/cache/keys/builders/render.js +7 -4
- package/esm/src/observability/tracing/otlp-setup.d.ts +2 -0
- package/esm/src/observability/tracing/otlp-setup.d.ts.map +1 -1
- package/esm/src/observability/tracing/otlp-setup.js +7 -1
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts +2 -2
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.js +12 -6
- package/esm/src/registry/project-scoped-registry-manager.d.ts +31 -1
- package/esm/src/registry/project-scoped-registry-manager.d.ts.map +1 -1
- package/esm/src/registry/project-scoped-registry-manager.js +241 -21
- package/esm/src/server/handlers/request/api/project-discovery.d.ts.map +1 -1
- package/esm/src/server/handlers/request/api/project-discovery.js +45 -41
- package/esm/src/tool/registry.d.ts.map +1 -1
- package/esm/src/tool/registry.js +14 -9
- package/esm/src/tool/remote-mcp.d.ts.map +1 -1
- package/esm/src/tool/remote-mcp.js +14 -2
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/esm/src/workflow/executor/step-executor.d.ts.map +1 -1
- package/esm/src/workflow/executor/step-executor.js +24 -4
- package/package.json +5 -5
|
@@ -22,15 +22,89 @@
|
|
|
22
22
|
*
|
|
23
23
|
* @module
|
|
24
24
|
*/
|
|
25
|
-
import {
|
|
25
|
+
import { tryGetRegistryScopeId } from "../cache/cache-key-builder.js";
|
|
26
26
|
import { agentLogger } from "../utils/logger/logger.js";
|
|
27
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
27
28
|
const DEFAULT_SCOPE_ID = "__default__";
|
|
29
|
+
const registryTransactionStorage = new AsyncLocalStorage();
|
|
30
|
+
const registryTransactionLocks = new Map();
|
|
31
|
+
async function acquireRegistryTransactionLock(scopeId) {
|
|
32
|
+
const previous = registryTransactionLocks.get(scopeId) ?? Promise.resolve();
|
|
33
|
+
const gate = Promise.withResolvers();
|
|
34
|
+
const current = previous.then(() => gate.promise);
|
|
35
|
+
registryTransactionLocks.set(scopeId, current);
|
|
36
|
+
await previous;
|
|
37
|
+
let released = false;
|
|
38
|
+
return () => {
|
|
39
|
+
if (released)
|
|
40
|
+
return;
|
|
41
|
+
released = true;
|
|
42
|
+
gate.resolve();
|
|
43
|
+
if (registryTransactionLocks.get(scopeId) === current) {
|
|
44
|
+
registryTransactionLocks.delete(scopeId);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
28
48
|
function buildRegistryScopeId() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
// tryGetRegistryScopeId() returns a project-isolated key even when
|
|
50
|
+
// tryGetCacheKeyContext() would return null (e.g. control-plane runs for an
|
|
51
|
+
// environment source without a pinned releaseId). Without this, all such
|
|
52
|
+
// runs collapse to "__default__", so concurrent projects can overwrite one
|
|
53
|
+
// another's registered primitives.
|
|
54
|
+
return tryGetRegistryScopeId() ?? DEFAULT_SCOPE_ID;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Stage project-scoped registry mutations and publish them as one synchronous
|
|
58
|
+
* commit after the callback succeeds. Reads inside the callback see staged
|
|
59
|
+
* state; concurrent requests keep seeing the previous live state.
|
|
60
|
+
* Transactions for the same scope are serialized. Live writes made outside
|
|
61
|
+
* the transaction while discovery is in flight are journaled alongside staged
|
|
62
|
+
* mutations and replayed in call order, preserving the previous immediate-
|
|
63
|
+
* mutation semantics without exposing a partially discovered generation.
|
|
64
|
+
* Use this for complete discovery generations, not incremental updates.
|
|
65
|
+
*
|
|
66
|
+
* Nested calls participate in the existing transaction. If a nested tenant
|
|
67
|
+
* context changes the registry scope, the first registry access throws rather
|
|
68
|
+
* than committing data into the wrong tenant.
|
|
69
|
+
*/
|
|
70
|
+
export async function runWithRegistryTransaction(fn) {
|
|
71
|
+
const existing = registryTransactionStorage.getStore();
|
|
72
|
+
if (existing?.state === "active")
|
|
73
|
+
return await fn();
|
|
74
|
+
const targetScopeId = buildRegistryScopeId();
|
|
75
|
+
const releaseLock = await acquireRegistryTransactionLock(targetScopeId);
|
|
76
|
+
const transaction = {
|
|
77
|
+
targetScopeId,
|
|
78
|
+
stages: new Map(),
|
|
79
|
+
state: "active",
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
return await registryTransactionStorage.run(transaction, async () => {
|
|
83
|
+
try {
|
|
84
|
+
const result = await fn();
|
|
85
|
+
// Prepare every manager before publishing any of them. Publication is
|
|
86
|
+
// synchronous, so no request can observe a partial generation.
|
|
87
|
+
const publications = Array.from(transaction.stages.values(), (stage) => stage.prepare());
|
|
88
|
+
for (const publication of publications) {
|
|
89
|
+
publication.publish();
|
|
90
|
+
}
|
|
91
|
+
transaction.state = "committed";
|
|
92
|
+
transaction.stages.clear();
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
transaction.state = "aborted";
|
|
97
|
+
for (const stage of transaction.stages.values()) {
|
|
98
|
+
stage.abort();
|
|
99
|
+
}
|
|
100
|
+
transaction.stages.clear();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
releaseLock();
|
|
32
107
|
}
|
|
33
|
-
return `${cacheContext.projectId}:${cacheContext.mode}:${cacheContext.versionId}`;
|
|
34
108
|
}
|
|
35
109
|
/**
|
|
36
110
|
* Base class for project-scoped registries.
|
|
@@ -39,10 +113,34 @@ function buildRegistryScopeId() {
|
|
|
39
113
|
*/
|
|
40
114
|
export class ProjectScopedRegistryManager {
|
|
41
115
|
registryName;
|
|
116
|
+
options;
|
|
42
117
|
registriesByScope = new Map();
|
|
43
118
|
sharedRegistry = new Map();
|
|
44
|
-
|
|
119
|
+
activeStagesByScope = new Map();
|
|
120
|
+
constructor(registryName, options = {}) {
|
|
45
121
|
this.registryName = registryName;
|
|
122
|
+
this.options = options;
|
|
123
|
+
}
|
|
124
|
+
validateRegistration(registry, id, incoming) {
|
|
125
|
+
if (!registry.has(id))
|
|
126
|
+
return;
|
|
127
|
+
this.options.validateRegistration?.(id, registry.get(id), incoming);
|
|
128
|
+
}
|
|
129
|
+
applyMutation(registry, mutation, validateRegistration = false) {
|
|
130
|
+
switch (mutation.type) {
|
|
131
|
+
case "clear":
|
|
132
|
+
registry.clear();
|
|
133
|
+
break;
|
|
134
|
+
case "delete":
|
|
135
|
+
registry.delete(mutation.id);
|
|
136
|
+
break;
|
|
137
|
+
case "set":
|
|
138
|
+
if (validateRegistration) {
|
|
139
|
+
this.validateRegistration(registry, mutation.id, mutation.item);
|
|
140
|
+
}
|
|
141
|
+
registry.set(mutation.id, mutation.item);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
46
144
|
}
|
|
47
145
|
/**
|
|
48
146
|
* Get the current project ID from AsyncLocalStorage context.
|
|
@@ -51,6 +149,82 @@ export class ProjectScopedRegistryManager {
|
|
|
51
149
|
getCurrentScopeId() {
|
|
52
150
|
return buildRegistryScopeId();
|
|
53
151
|
}
|
|
152
|
+
/** Return the transaction-local stage for this manager and scope. */
|
|
153
|
+
getTransactionStage(scopeId) {
|
|
154
|
+
const transaction = registryTransactionStorage.getStore();
|
|
155
|
+
if (!transaction)
|
|
156
|
+
return undefined;
|
|
157
|
+
if (transaction.state === "committed")
|
|
158
|
+
return undefined;
|
|
159
|
+
if (transaction.state === "aborted") {
|
|
160
|
+
throw new Error(`[${this.registryName}] Registry transaction already aborted for scope ` +
|
|
161
|
+
`"${transaction.targetScopeId}"`);
|
|
162
|
+
}
|
|
163
|
+
if (scopeId !== transaction.targetScopeId) {
|
|
164
|
+
throw new Error(`[${this.registryName}] Registry scope changed during transaction: ` +
|
|
165
|
+
`expected "${transaction.targetScopeId}", got "${scopeId}"`);
|
|
166
|
+
}
|
|
167
|
+
const existing = transaction.stages.get(this);
|
|
168
|
+
if (existing)
|
|
169
|
+
return existing;
|
|
170
|
+
const baseRegistry = new Map(this.registriesByScope.get(scopeId));
|
|
171
|
+
const registry = new Map(baseRegistry);
|
|
172
|
+
const validationRegistry = new Map(baseRegistry);
|
|
173
|
+
const mutations = [];
|
|
174
|
+
let closed = false;
|
|
175
|
+
const close = () => {
|
|
176
|
+
if (closed)
|
|
177
|
+
return;
|
|
178
|
+
closed = true;
|
|
179
|
+
const activeStages = this.activeStagesByScope.get(scopeId);
|
|
180
|
+
activeStages?.delete(stage);
|
|
181
|
+
if (activeStages?.size === 0)
|
|
182
|
+
this.activeStagesByScope.delete(scopeId);
|
|
183
|
+
};
|
|
184
|
+
const stage = {
|
|
185
|
+
registry,
|
|
186
|
+
validateRegistration: (id, incoming) => {
|
|
187
|
+
this.validateRegistration(validationRegistry, id, incoming);
|
|
188
|
+
},
|
|
189
|
+
record: (mutation) => {
|
|
190
|
+
mutations.push(mutation);
|
|
191
|
+
this.applyMutation(validationRegistry, mutation);
|
|
192
|
+
},
|
|
193
|
+
prepare: () => {
|
|
194
|
+
const replacement = new Map(baseRegistry);
|
|
195
|
+
for (const mutation of mutations) {
|
|
196
|
+
this.applyMutation(replacement, mutation, true);
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
publish: () => {
|
|
200
|
+
close();
|
|
201
|
+
if (replacement.size === 0) {
|
|
202
|
+
this.registriesByScope.delete(scopeId);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.registriesByScope.set(scopeId, replacement);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
abort: close,
|
|
211
|
+
};
|
|
212
|
+
transaction.stages.set(this, stage);
|
|
213
|
+
const activeStages = this.activeStagesByScope.get(scopeId) ?? new Set();
|
|
214
|
+
activeStages.add(stage);
|
|
215
|
+
this.activeStagesByScope.set(scopeId, activeStages);
|
|
216
|
+
return stage;
|
|
217
|
+
}
|
|
218
|
+
/** Record a live mutation in any in-flight transaction for this scope. */
|
|
219
|
+
recordLiveMutation(scopeId, mutation) {
|
|
220
|
+
for (const stage of this.activeStagesByScope.get(scopeId) ?? []) {
|
|
221
|
+
stage.record(mutation);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** Read the active registry, routing transaction access to its staged copy. */
|
|
225
|
+
getActiveScopeRegistry(scopeId) {
|
|
226
|
+
return this.getTransactionStage(scopeId)?.registry ?? this.registriesByScope.get(scopeId);
|
|
227
|
+
}
|
|
54
228
|
/**
|
|
55
229
|
* Get or create registry for a specific project.
|
|
56
230
|
*/
|
|
@@ -67,11 +241,21 @@ export class ProjectScopedRegistryManager {
|
|
|
67
241
|
*/
|
|
68
242
|
register(id, item) {
|
|
69
243
|
const scopeId = this.getCurrentScopeId();
|
|
70
|
-
const
|
|
244
|
+
const stage = this.getTransactionStage(scopeId);
|
|
245
|
+
const registry = stage?.registry ?? this.getScopeRegistry(scopeId);
|
|
246
|
+
if (stage)
|
|
247
|
+
stage.validateRegistration(id, item);
|
|
248
|
+
else
|
|
249
|
+
this.validateRegistration(registry, id, item);
|
|
71
250
|
if (registry.has(id)) {
|
|
72
251
|
agentLogger.debug(`[${this.registryName}] "${id}" already registered for scope ${scopeId}. Overwriting.`);
|
|
73
252
|
}
|
|
74
253
|
registry.set(id, item);
|
|
254
|
+
const mutation = { type: "set", id, item };
|
|
255
|
+
if (stage)
|
|
256
|
+
stage.record(mutation);
|
|
257
|
+
else
|
|
258
|
+
this.recordLiveMutation(scopeId, mutation);
|
|
75
259
|
agentLogger.debug(`[${this.registryName}] Registered "${id}" for scope ${scopeId}`);
|
|
76
260
|
}
|
|
77
261
|
/**
|
|
@@ -79,6 +263,9 @@ export class ProjectScopedRegistryManager {
|
|
|
79
263
|
* Use for framework-provided tools, not user-defined ones.
|
|
80
264
|
*/
|
|
81
265
|
registerShared(id, item) {
|
|
266
|
+
// Shared framework infrastructure is intentionally process-wide and is
|
|
267
|
+
// published immediately even inside a project transaction. Project
|
|
268
|
+
// discovery must never use this method for tenant-owned definitions.
|
|
82
269
|
if (this.sharedRegistry.has(id)) {
|
|
83
270
|
agentLogger.debug(`[${this.registryName}] Shared "${id}" already registered. Overwriting.`);
|
|
84
271
|
}
|
|
@@ -91,7 +278,7 @@ export class ProjectScopedRegistryManager {
|
|
|
91
278
|
*/
|
|
92
279
|
get(id) {
|
|
93
280
|
const scopeId = this.getCurrentScopeId();
|
|
94
|
-
return this.
|
|
281
|
+
return this.getActiveScopeRegistry(scopeId)?.get(id) ?? this.sharedRegistry.get(id);
|
|
95
282
|
}
|
|
96
283
|
/**
|
|
97
284
|
* Get item registered in the current project's own scope, without falling
|
|
@@ -101,14 +288,14 @@ export class ProjectScopedRegistryManager {
|
|
|
101
288
|
*/
|
|
102
289
|
getOwn(id) {
|
|
103
290
|
const scopeId = this.getCurrentScopeId();
|
|
104
|
-
return this.
|
|
291
|
+
return this.getActiveScopeRegistry(scopeId)?.get(id);
|
|
105
292
|
}
|
|
106
293
|
/**
|
|
107
294
|
* Check if item exists for the current project.
|
|
108
295
|
*/
|
|
109
296
|
has(id) {
|
|
110
297
|
const scopeId = this.getCurrentScopeId();
|
|
111
|
-
return (this.
|
|
298
|
+
return (this.getActiveScopeRegistry(scopeId)?.has(id) ?? false) ||
|
|
112
299
|
this.sharedRegistry.has(id);
|
|
113
300
|
}
|
|
114
301
|
/**
|
|
@@ -116,7 +303,7 @@ export class ProjectScopedRegistryManager {
|
|
|
116
303
|
*/
|
|
117
304
|
getAllIds() {
|
|
118
305
|
const scopeId = this.getCurrentScopeId();
|
|
119
|
-
const projectIds = this.
|
|
306
|
+
const projectIds = this.getActiveScopeRegistry(scopeId)?.keys() ?? [];
|
|
120
307
|
const sharedIds = this.sharedRegistry.keys();
|
|
121
308
|
return Array.from(new Set([...projectIds, ...sharedIds]));
|
|
122
309
|
}
|
|
@@ -125,7 +312,7 @@ export class ProjectScopedRegistryManager {
|
|
|
125
312
|
*/
|
|
126
313
|
getAll() {
|
|
127
314
|
const scopeId = this.getCurrentScopeId();
|
|
128
|
-
const projectRegistry = this.
|
|
315
|
+
const projectRegistry = this.getActiveScopeRegistry(scopeId);
|
|
129
316
|
if (!projectRegistry)
|
|
130
317
|
return new Map(this.sharedRegistry);
|
|
131
318
|
const result = new Map(this.sharedRegistry);
|
|
@@ -138,28 +325,50 @@ export class ProjectScopedRegistryManager {
|
|
|
138
325
|
*/
|
|
139
326
|
delete(id) {
|
|
140
327
|
const scopeId = this.getCurrentScopeId();
|
|
141
|
-
const
|
|
142
|
-
|
|
328
|
+
const stage = this.getTransactionStage(scopeId);
|
|
329
|
+
const registry = stage?.registry ?? this.registriesByScope.get(scopeId);
|
|
330
|
+
const existed = registry?.has(id) ?? false;
|
|
331
|
+
if (!existed && !stage && !this.activeStagesByScope.has(scopeId))
|
|
143
332
|
return false;
|
|
144
|
-
registry
|
|
333
|
+
registry?.delete(id);
|
|
334
|
+
const mutation = { type: "delete", id };
|
|
335
|
+
if (stage)
|
|
336
|
+
stage.record(mutation);
|
|
337
|
+
else
|
|
338
|
+
this.recordLiveMutation(scopeId, mutation);
|
|
145
339
|
agentLogger.debug(`[${this.registryName}] Deleted "${id}" from scope ${scopeId}`);
|
|
146
|
-
return
|
|
340
|
+
return existed;
|
|
147
341
|
}
|
|
148
342
|
/**
|
|
149
343
|
* Clear all items for the current project.
|
|
150
344
|
*/
|
|
151
345
|
clear() {
|
|
152
|
-
this.
|
|
346
|
+
const scopeId = this.getCurrentScopeId();
|
|
347
|
+
const stage = this.getTransactionStage(scopeId);
|
|
348
|
+
if (stage) {
|
|
349
|
+
stage.registry.clear();
|
|
350
|
+
stage.record({ type: "clear" });
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.clearProject(scopeId);
|
|
153
354
|
}
|
|
154
355
|
/**
|
|
155
356
|
* Clear a specific project's registry.
|
|
156
357
|
*/
|
|
157
358
|
clearProject(projectId) {
|
|
359
|
+
const transaction = registryTransactionStorage.getStore();
|
|
360
|
+
if (transaction && transaction.state !== "committed") {
|
|
361
|
+
throw new Error(`[${this.registryName}] clearProject() is not supported during a registry transaction`);
|
|
362
|
+
}
|
|
158
363
|
let cleared = false;
|
|
159
|
-
|
|
364
|
+
const scopeIds = new Set([
|
|
365
|
+
...this.registriesByScope.keys(),
|
|
366
|
+
...this.activeStagesByScope.keys(),
|
|
367
|
+
]);
|
|
368
|
+
for (const scopeId of scopeIds) {
|
|
160
369
|
if (scopeId === projectId || scopeId.startsWith(`${projectId}:`)) {
|
|
161
|
-
this.registriesByScope.delete(scopeId);
|
|
162
|
-
|
|
370
|
+
cleared = this.registriesByScope.delete(scopeId) || cleared;
|
|
371
|
+
this.recordLiveMutation(scopeId, { type: "clear" });
|
|
163
372
|
}
|
|
164
373
|
}
|
|
165
374
|
if (cleared) {
|
|
@@ -170,6 +379,17 @@ export class ProjectScopedRegistryManager {
|
|
|
170
379
|
* Clear everything (for testing).
|
|
171
380
|
*/
|
|
172
381
|
clearAll() {
|
|
382
|
+
const transaction = registryTransactionStorage.getStore();
|
|
383
|
+
if (transaction && transaction.state !== "committed") {
|
|
384
|
+
throw new Error(`[${this.registryName}] clearAll() is not supported during a registry transaction`);
|
|
385
|
+
}
|
|
386
|
+
const scopeIds = new Set([
|
|
387
|
+
...this.registriesByScope.keys(),
|
|
388
|
+
...this.activeStagesByScope.keys(),
|
|
389
|
+
]);
|
|
390
|
+
for (const scopeId of scopeIds) {
|
|
391
|
+
this.recordLiveMutation(scopeId, { type: "clear" });
|
|
392
|
+
}
|
|
173
393
|
this.registriesByScope.clear();
|
|
174
394
|
this.sharedRegistry.clear();
|
|
175
395
|
agentLogger.debug(`[${this.registryName}] Cleared all registries`);
|
|
@@ -185,7 +405,7 @@ export class ProjectScopedRegistryManager {
|
|
|
185
405
|
projectCount: this.registriesByScope.size,
|
|
186
406
|
sharedCount: this.sharedRegistry.size,
|
|
187
407
|
totalItems,
|
|
188
|
-
currentProjectItems: this.
|
|
408
|
+
currentProjectItems: this.getActiveScopeRegistry(scopeId)?.size ?? 0,
|
|
189
409
|
};
|
|
190
410
|
}
|
|
191
411
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-discovery.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/api/project-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"project-discovery.d.ts","sourceRoot":"","sources":["../../../../../../src/src/server/handlers/request/api/project-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAMtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAyHrD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC,CAmF1F"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { serverLogger } from "../../../../utils/index.js";
|
|
2
2
|
import { clearTrackedAgents, createProjectDiscoveryConfig } from "../../../../discovery/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { tryGetRegistryScopeContext } from "../../../../cache/cache-key-builder.js";
|
|
4
|
+
import { runWithRegistryTransaction } from "../../../../registry/project-scoped-registry-manager.js";
|
|
4
5
|
import { sanitizeUrlCredentials } from "../../../../utils/logger/redact.js";
|
|
5
6
|
const logger = serverLogger.component("api-wrapper");
|
|
6
7
|
const discoveredProjects = new Map();
|
|
@@ -61,9 +62,9 @@ function summarizeDiscoveryFailures(errors, projectDir) {
|
|
|
61
62
|
}
|
|
62
63
|
/** Build a discovery cache key that incorporates the release/version. */
|
|
63
64
|
function discoveryKey(ctx) {
|
|
64
|
-
const
|
|
65
|
-
if (
|
|
66
|
-
return
|
|
65
|
+
const registryScope = tryGetRegistryScopeContext();
|
|
66
|
+
if (registryScope) {
|
|
67
|
+
return registryScope.scopeId;
|
|
67
68
|
}
|
|
68
69
|
const slug = ctx.projectSlug ?? ctx.projectDir;
|
|
69
70
|
const environment = ctx.enriched?.environment ?? ctx.resolvedEnvironment ??
|
|
@@ -76,9 +77,9 @@ function discoveryKey(ctx) {
|
|
|
76
77
|
return `${slug}:preview:${branch}`;
|
|
77
78
|
}
|
|
78
79
|
function shouldCacheCompletedDiscovery(ctx) {
|
|
79
|
-
const
|
|
80
|
-
if (
|
|
81
|
-
return
|
|
80
|
+
const registryScope = tryGetRegistryScopeContext();
|
|
81
|
+
if (registryScope) {
|
|
82
|
+
return registryScope.immutable;
|
|
82
83
|
}
|
|
83
84
|
const environment = ctx.enriched?.environment ?? ctx.resolvedEnvironment ??
|
|
84
85
|
(ctx.releaseId ? "production" : "preview");
|
|
@@ -101,41 +102,44 @@ export async function ensureProjectDiscovery(ctx) {
|
|
|
101
102
|
const { clearTranspileCache, discoverAll } = await import("../../../../discovery/index.js");
|
|
102
103
|
const { agentRegistry } = await import("../../../../agent/composition/composition.js");
|
|
103
104
|
const { toolRegistry } = await import("../../../../tool/registry.js");
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const shouldWarnOnEmptyAiDiscovery = discoveryOptions.toolDirs.length > 0 ||
|
|
117
|
-
discoveryOptions.agentDirs.length > 0;
|
|
118
|
-
const logData = {
|
|
119
|
-
projectSlug: ctx.projectSlug,
|
|
120
|
-
releaseId: ctx.releaseId,
|
|
121
|
-
agents: result.agents.size,
|
|
122
|
-
tools: result.tools.size,
|
|
123
|
-
errors: result.errors.length,
|
|
124
|
-
};
|
|
125
|
-
if (result.errors.length > 0) {
|
|
126
|
-
logger.warn("Primitive discovery completed with errors", {
|
|
127
|
-
...logData,
|
|
128
|
-
failures: summarizeDiscoveryFailures(result.errors, ctx.projectDir),
|
|
129
|
-
omittedErrors: Math.max(0, result.errors.length - MAX_DISCOVERY_FAILURES_TO_LOG),
|
|
105
|
+
return await runWithRegistryTransaction(async () => {
|
|
106
|
+
// Clear stale entries in a transaction-local copy. Concurrent runs keep
|
|
107
|
+
// using the prior live registry until discovery succeeds and the staged
|
|
108
|
+
// replacement is committed atomically.
|
|
109
|
+
clearTrackedAgents();
|
|
110
|
+
clearTranspileCache();
|
|
111
|
+
agentRegistry.clear();
|
|
112
|
+
toolRegistry.clear();
|
|
113
|
+
const discoveryOptions = createProjectDiscoveryConfig({
|
|
114
|
+
projectDir: ctx.projectDir,
|
|
115
|
+
config: ctx.config,
|
|
116
|
+
fsAdapter: ctx.adapter.fs,
|
|
130
117
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
118
|
+
const result = await discoverAll(discoveryOptions);
|
|
119
|
+
const shouldWarnOnEmptyAiDiscovery = discoveryOptions.toolDirs.length > 0 ||
|
|
120
|
+
discoveryOptions.agentDirs.length > 0;
|
|
121
|
+
const logData = {
|
|
122
|
+
projectSlug: ctx.projectSlug,
|
|
123
|
+
releaseId: ctx.releaseId,
|
|
124
|
+
agents: result.agents.size,
|
|
125
|
+
tools: result.tools.size,
|
|
126
|
+
errors: result.errors.length,
|
|
127
|
+
};
|
|
128
|
+
if (result.errors.length > 0) {
|
|
129
|
+
logger.warn("Primitive discovery completed with errors", {
|
|
130
|
+
...logData,
|
|
131
|
+
failures: summarizeDiscoveryFailures(result.errors, ctx.projectDir),
|
|
132
|
+
omittedErrors: Math.max(0, result.errors.length - MAX_DISCOVERY_FAILURES_TO_LOG),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else if (result.agents.size === 0 && result.tools.size === 0 && shouldWarnOnEmptyAiDiscovery) {
|
|
136
|
+
logger.info("Primitive discovery found 0 agents and 0 tools", logData);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
logger.info("Primitive discovery completed", logData);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
});
|
|
139
143
|
})(),
|
|
140
144
|
};
|
|
141
145
|
discoveredProjects.set(key, discovery);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/src/tool/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/src/tool/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AA2B7E,cAAM,iBAAkB,SAAQ,oBAAoB,CAAC,IAAI,CAAC;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI;IAW/C,mBAAmB,IAAI,cAAc,EAAE;CAGxC;AAED,kCAAkC;AAClC,eAAO,MAAM,YAAY,mBAAqC,CAAC;AAE/D,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,CAenE"}
|
package/esm/src/tool/registry.js
CHANGED
|
@@ -3,7 +3,6 @@ import { agentLogger } from "../utils/logger/logger.js";
|
|
|
3
3
|
import { ScopedRegistryFacade } from "../registry/scoped-registry-facade.js";
|
|
4
4
|
import { ProjectScopedRegistryManager } from "../registry/project-scoped-registry-manager.js";
|
|
5
5
|
import { TOOL_ID_CONFLICT } from "../errors/error-registry/agent.js";
|
|
6
|
-
const toolManager = new ProjectScopedRegistryManager("tool");
|
|
7
6
|
/**
|
|
8
7
|
* Returns true when `incoming` is considered the same definition as `existing`:
|
|
9
8
|
* same object reference, or matching id + description. Equivalent definitions
|
|
@@ -14,17 +13,23 @@ function isSameToolDefinition(existing, incoming) {
|
|
|
14
13
|
return existing === incoming ||
|
|
15
14
|
(existing.id === incoming.id && existing.description === incoming.description);
|
|
16
15
|
}
|
|
16
|
+
function validateToolRegistration(id, existing, incoming) {
|
|
17
|
+
if (isSameToolDefinition(existing, incoming))
|
|
18
|
+
return;
|
|
19
|
+
throw TOOL_ID_CONFLICT.create({
|
|
20
|
+
detail: `Tool "${id}" is already registered with a different definition. Use a unique tool ID or rename one of the conflicting tools.`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const toolManager = new ProjectScopedRegistryManager("tool", {
|
|
24
|
+
validateRegistration: validateToolRegistration,
|
|
25
|
+
});
|
|
17
26
|
class ToolRegistryClass extends ScopedRegistryFacade {
|
|
18
27
|
register(id, item) {
|
|
19
|
-
//
|
|
20
|
-
//
|
|
28
|
+
// Equivalent-registration diagnostics inspect the project scope only;
|
|
29
|
+
// the manager enforces conflicts here and again against journaled order.
|
|
30
|
+
// Shared/framework tools remain intentionally shadowable.
|
|
21
31
|
const existing = this.getOwn(id);
|
|
22
|
-
if (existing !== undefined &&
|
|
23
|
-
throw TOOL_ID_CONFLICT.create({
|
|
24
|
-
detail: `Tool "${id}" is already registered with a different definition. Use a unique tool ID or rename one of the conflicting tools.`,
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
if (existing !== undefined && existing !== item) {
|
|
32
|
+
if (existing !== undefined && existing !== item && isSameToolDefinition(existing, item)) {
|
|
28
33
|
agentLogger.debug(`[tool] "${id}" re-registered with equivalent definition; replacing.`);
|
|
29
34
|
}
|
|
30
35
|
super.register(id, item);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-mcp.d.ts","sourceRoot":"","sources":["../../../src/src/tool/remote-mcp.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AASzF,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,oBAAoB,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnF,oDAAoD;AACpD,MAAM,WAAW,yBAAyB;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,eAAe,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IACnD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;
|
|
1
|
+
{"version":3,"file":"remote-mcp.d.ts","sourceRoot":"","sources":["../../../src/src/tool/remote-mcp.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,oBAAoB,EAAE,MAAM,YAAY,CAAC;AASzF,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,oBAAoB,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AAEnF,oDAAoD;AACpD,MAAM,WAAW,yBAAyB;IACxC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAClC,OAAO,CAAC,EAAE,eAAe,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IACnD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAucD,qCAAqC;AACrC,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,yBAAyB,GAChC,gBAAgB,CA0FlB"}
|
|
@@ -139,6 +139,18 @@ function normalizeKnownToolError(value, toolName, endpoint, context) {
|
|
|
139
139
|
message: `${label} needs to be reconnected before this tool can run.`,
|
|
140
140
|
};
|
|
141
141
|
}
|
|
142
|
+
function preserveToolExecutionErrorMarker(value) {
|
|
143
|
+
if (isRecord(value) && !Array.isArray(value)) {
|
|
144
|
+
return hasToolExecutionErrorMarker(value) ? value : { ...value, isError: true };
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
isError: true,
|
|
148
|
+
message: typeof value === "string" && value.trim().length > 0
|
|
149
|
+
? value
|
|
150
|
+
: "Remote MCP tool returned an error",
|
|
151
|
+
...(value === undefined ? {} : { output: value }),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
142
154
|
function isReconnectRequiredToolOutput(value) {
|
|
143
155
|
return isRecord(value) && value.error === "reconnect_required";
|
|
144
156
|
}
|
|
@@ -296,7 +308,7 @@ function normalizeCallToolResult(input) {
|
|
|
296
308
|
const errorBody = "structuredContent" in result
|
|
297
309
|
? result.structuredContent
|
|
298
310
|
: parseJsonText(text) ?? { error: "tool_error", message: text };
|
|
299
|
-
return normalizeKnownToolError(errorBody, input.toolName, input.endpoint, input.context);
|
|
311
|
+
return preserveToolExecutionErrorMarker(normalizeKnownToolError(errorBody, input.toolName, input.endpoint, input.context));
|
|
300
312
|
}
|
|
301
313
|
if ("structuredContent" in result) {
|
|
302
314
|
return result.structuredContent;
|
|
@@ -305,7 +317,7 @@ function normalizeCallToolResult(input) {
|
|
|
305
317
|
}
|
|
306
318
|
if (isError) {
|
|
307
319
|
const errorBody = "structuredContent" in result ? result.structuredContent : result;
|
|
308
|
-
return normalizeKnownToolError(errorBody, input.toolName, input.endpoint, input.context);
|
|
320
|
+
return preserveToolExecutionErrorMarker(normalizeKnownToolError(errorBody, input.toolName, input.endpoint, input.context));
|
|
309
321
|
}
|
|
310
322
|
if ("structuredContent" in result) {
|
|
311
323
|
return result.structuredContent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-executor.d.ts","sourceRoot":"","sources":["../../../../src/src/workflow/executor/step-executor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAiB,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"step-executor.d.ts","sourceRoot":"","sources":["../../../../src/src/workflow/executor/step-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import { runWithCacheKeyContext, } from "../../cache/cache-key-builder.js";
|
|
2
|
+
import { runWithCacheKeyContext, runWithoutCacheKeyContext, } from "../../cache/cache-key-builder.js";
|
|
3
|
+
import { runWithRequestContext } from "../../platform/adapters/fs/veryfront/request-context.js";
|
|
3
4
|
import { ensureError } from "../../errors/veryfront-error.js";
|
|
4
5
|
import { isVeryfrontError } from "../../errors/http-error.js";
|
|
5
6
|
import { AGENT_NOT_FOUND, INITIALIZATION_ERROR, INVALID_ARGUMENT, ORCHESTRATION_ERROR, RESOURCE_NOT_FOUND, TIMEOUT_ERROR, } from "../../errors/error-registry.js";
|
|
@@ -22,10 +23,16 @@ export function getWorkflowTenant() {
|
|
|
22
23
|
}
|
|
23
24
|
function cacheKeyContextFromWorkflowTenant(tenant) {
|
|
24
25
|
const mode = tenant.productionMode ? "production" : "preview";
|
|
26
|
+
// Environment sources are mutable and have no immutable version segment.
|
|
27
|
+
// A synthetic "latest" distributed-cache bucket can mix different source
|
|
28
|
+
// snapshots, so these tenants use request context for registry isolation and
|
|
29
|
+
// deliberately skip distributed caching.
|
|
30
|
+
if (mode === "production" && !tenant.releaseId)
|
|
31
|
+
return null;
|
|
25
32
|
return {
|
|
26
|
-
projectId: tenant.projectId || tenant.projectSlug
|
|
33
|
+
projectId: tenant.projectId || tenant.projectSlug,
|
|
27
34
|
mode,
|
|
28
|
-
versionId: mode === "production" ?
|
|
35
|
+
versionId: mode === "production" ? tenant.releaseId : (tenant.branch || "main"),
|
|
29
36
|
};
|
|
30
37
|
}
|
|
31
38
|
/**
|
|
@@ -35,7 +42,20 @@ function cacheKeyContextFromWorkflowTenant(tenant) {
|
|
|
35
42
|
export function runWithWorkflowTenant(tenant, fn) {
|
|
36
43
|
if (!tenant)
|
|
37
44
|
return fn();
|
|
38
|
-
return workflowTenantStorage.run(tenant, () =>
|
|
45
|
+
return workflowTenantStorage.run(tenant, () => runWithRequestContext({
|
|
46
|
+
projectSlug: tenant.projectSlug,
|
|
47
|
+
token: tenant.token,
|
|
48
|
+
projectId: tenant.projectId,
|
|
49
|
+
productionMode: tenant.productionMode,
|
|
50
|
+
releaseId: tenant.releaseId,
|
|
51
|
+
branch: tenant.branch,
|
|
52
|
+
environmentName: tenant.environmentName,
|
|
53
|
+
}, () => {
|
|
54
|
+
const cacheContext = cacheKeyContextFromWorkflowTenant(tenant);
|
|
55
|
+
return cacheContext
|
|
56
|
+
? runWithCacheKeyContext(cacheContext, fn)
|
|
57
|
+
: runWithoutCacheKeyContext(fn);
|
|
58
|
+
}));
|
|
39
59
|
}
|
|
40
60
|
/** Default initial delay before first retry attempt */
|
|
41
61
|
const DEFAULT_RETRY_INITIAL_DELAY_MS = 1_000;
|