donobu 5.36.1 → 5.36.2
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/esm/managers/DonobuFlowsManager.d.ts +21 -1
- package/dist/esm/managers/DonobuFlowsManager.js +51 -2
- package/dist/esm/managers/DonobuStack.js +4 -4
- package/dist/esm/managers/FederatedPagination.d.ts +43 -13
- package/dist/esm/managers/FederatedPagination.js +122 -39
- package/dist/esm/managers/SuitesManager.js +11 -6
- package/dist/esm/managers/TestsManager.d.ts +20 -2
- package/dist/esm/managers/TestsManager.js +67 -14
- package/dist/esm/persistence/flows/FlowsPersistenceDonobuApi.js +10 -1
- package/dist/esm/persistence/flows/FlowsPersistenceRegistry.d.ts +25 -1
- package/dist/esm/persistence/flows/FlowsPersistenceRegistry.js +17 -5
- package/dist/esm/persistence/suites/SuitesPersistenceRegistry.d.ts +18 -1
- package/dist/esm/persistence/suites/SuitesPersistenceRegistry.js +17 -5
- package/dist/esm/persistence/tests/TestsPersistenceRegistry.d.ts +18 -1
- package/dist/esm/persistence/tests/TestsPersistenceRegistry.js +20 -5
- package/dist/esm/tools/ScrollPageTool.js +1 -1
- package/dist/managers/DonobuFlowsManager.d.ts +21 -1
- package/dist/managers/DonobuFlowsManager.js +51 -2
- package/dist/managers/DonobuStack.js +4 -4
- package/dist/managers/FederatedPagination.d.ts +43 -13
- package/dist/managers/FederatedPagination.js +122 -39
- package/dist/managers/SuitesManager.js +11 -6
- package/dist/managers/TestsManager.d.ts +20 -2
- package/dist/managers/TestsManager.js +67 -14
- package/dist/persistence/flows/FlowsPersistenceDonobuApi.js +10 -1
- package/dist/persistence/flows/FlowsPersistenceRegistry.d.ts +25 -1
- package/dist/persistence/flows/FlowsPersistenceRegistry.js +17 -5
- package/dist/persistence/suites/SuitesPersistenceRegistry.d.ts +18 -1
- package/dist/persistence/suites/SuitesPersistenceRegistry.js +17 -5
- package/dist/persistence/tests/TestsPersistenceRegistry.d.ts +18 -1
- package/dist/persistence/tests/TestsPersistenceRegistry.js +20 -5
- package/dist/tools/ScrollPageTool.js +1 -1
- package/package.json +1 -1
|
@@ -125,7 +125,16 @@ class FlowsPersistenceDonobuApi {
|
|
|
125
125
|
params.set('orphaned', query.orphaned ? 'true' : 'false');
|
|
126
126
|
}
|
|
127
127
|
if (query.sortBy) {
|
|
128
|
-
|
|
128
|
+
switch (query.sortBy) {
|
|
129
|
+
case 'created_at':
|
|
130
|
+
// `donobu-api` only supports `started_at`, but that's essentially
|
|
131
|
+
// equivalent to `created_at`
|
|
132
|
+
params.set('sort_by', 'started_at');
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
params.set('sort_by', query.sortBy);
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
129
138
|
}
|
|
130
139
|
if (query.sortOrder) {
|
|
131
140
|
params.set('sort_order', query.sortOrder);
|
|
@@ -2,6 +2,17 @@ import type { EnvPick } from 'env-struct';
|
|
|
2
2
|
import type { env } from '../../envVars';
|
|
3
3
|
import { PersistencePluginRegistry } from '../PersistencePlugin';
|
|
4
4
|
import type { FlowsPersistence } from './FlowsPersistence';
|
|
5
|
+
/**
|
|
6
|
+
* A persistence layer paired with the `PERSISTENCE_PRIORITY` key it was
|
|
7
|
+
* created from. The key is shared across the flows/tests/suites registries:
|
|
8
|
+
* the same key in each registry refers to the same logical store, which is
|
|
9
|
+
* how cross-entity foreign keys are kept aligned (e.g. a flow's `testId`
|
|
10
|
+
* resolves to a test that lives in the same layer).
|
|
11
|
+
*/
|
|
12
|
+
export interface FlowsPersistenceLayer {
|
|
13
|
+
key: string;
|
|
14
|
+
persistence: FlowsPersistence;
|
|
15
|
+
}
|
|
5
16
|
export interface FlowsPersistenceRegistry {
|
|
6
17
|
/**
|
|
7
18
|
* Returns the primary persistence layer.
|
|
@@ -9,6 +20,17 @@ export interface FlowsPersistenceRegistry {
|
|
|
9
20
|
get(): Promise<FlowsPersistence>;
|
|
10
21
|
/** Returns a list of all valid persistence layers. Guaranteed to be non-empty. */
|
|
11
22
|
getAll(): Promise<FlowsPersistence[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Returns all valid layers paired with their `PERSISTENCE_PRIORITY` key.
|
|
25
|
+
* Used by callers that need to align writes across registries (e.g. write
|
|
26
|
+
* a flow into the same layer as the test it references).
|
|
27
|
+
*/
|
|
28
|
+
getEntries(): Promise<FlowsPersistenceLayer[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Returns the persistence layer registered under the given
|
|
31
|
+
* `PERSISTENCE_PRIORITY` key, or undefined if no such layer exists.
|
|
32
|
+
*/
|
|
33
|
+
getByKey(key: string): Promise<FlowsPersistence | undefined>;
|
|
12
34
|
}
|
|
13
35
|
/**
|
|
14
36
|
* A factory class for creating FlowsPersistence instances. Persistence layers are constructed
|
|
@@ -19,7 +41,7 @@ export declare class FlowsPersistenceRegistryImpl implements FlowsPersistenceReg
|
|
|
19
41
|
/**
|
|
20
42
|
* Creates an instance with pre-built persistence layers.
|
|
21
43
|
*/
|
|
22
|
-
constructor(layers:
|
|
44
|
+
constructor(layers: FlowsPersistenceLayer[]);
|
|
23
45
|
/**
|
|
24
46
|
* Creates an instance by reading environment variables and eagerly constructing
|
|
25
47
|
* all applicable persistence layers.
|
|
@@ -31,5 +53,7 @@ export declare class FlowsPersistenceRegistryImpl implements FlowsPersistenceReg
|
|
|
31
53
|
get(): Promise<FlowsPersistence>;
|
|
32
54
|
/** Returns all persistence layers. Guaranteed to be non-empty. */
|
|
33
55
|
getAll(): Promise<FlowsPersistence[]>;
|
|
56
|
+
getEntries(): Promise<FlowsPersistenceLayer[]>;
|
|
57
|
+
getByKey(key: string): Promise<FlowsPersistence | undefined>;
|
|
34
58
|
}
|
|
35
59
|
//# sourceMappingURL=FlowsPersistenceRegistry.d.ts.map
|
|
@@ -32,21 +32,27 @@ class FlowsPersistenceRegistryImpl {
|
|
|
32
32
|
switch (key) {
|
|
33
33
|
case 'DONOBU':
|
|
34
34
|
if (donobuApiKey && donobuApiBaseUrl) {
|
|
35
|
-
layers.push(
|
|
35
|
+
layers.push({
|
|
36
|
+
key,
|
|
37
|
+
persistence: new FlowsPersistenceDonobuApi_1.FlowsPersistenceDonobuApi(donobuApiBaseUrl, donobuApiKey),
|
|
38
|
+
});
|
|
36
39
|
}
|
|
37
40
|
break;
|
|
38
41
|
case 'LOCAL':
|
|
39
|
-
layers.push(
|
|
42
|
+
layers.push({
|
|
43
|
+
key,
|
|
44
|
+
persistence: await FlowsPersistenceSqlite_1.FlowsPersistenceSqlite.create(await (0, DonobuSqliteDb_1.getDonobuSqliteDatabase)()),
|
|
45
|
+
});
|
|
40
46
|
break;
|
|
41
47
|
case 'RAM':
|
|
42
|
-
layers.push(new FlowsPersistenceVolatile_1.FlowsPersistenceVolatile());
|
|
48
|
+
layers.push({ key, persistence: new FlowsPersistenceVolatile_1.FlowsPersistenceVolatile() });
|
|
43
49
|
break;
|
|
44
50
|
default: {
|
|
45
51
|
const plugin = persistencePlugins.get(key);
|
|
46
52
|
if (plugin) {
|
|
47
53
|
const impl = await plugin.createFlowsPersistence();
|
|
48
54
|
if (impl) {
|
|
49
|
-
layers.push(impl);
|
|
55
|
+
layers.push({ key, persistence: impl });
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
break;
|
|
@@ -59,12 +65,18 @@ class FlowsPersistenceRegistryImpl {
|
|
|
59
65
|
* Returns the primary persistence layer.
|
|
60
66
|
*/
|
|
61
67
|
async get() {
|
|
62
|
-
return this.layers[0];
|
|
68
|
+
return this.layers[0].persistence;
|
|
63
69
|
}
|
|
64
70
|
/** Returns all persistence layers. Guaranteed to be non-empty. */
|
|
65
71
|
async getAll() {
|
|
72
|
+
return this.layers.map((layer) => layer.persistence);
|
|
73
|
+
}
|
|
74
|
+
async getEntries() {
|
|
66
75
|
return this.layers;
|
|
67
76
|
}
|
|
77
|
+
async getByKey(key) {
|
|
78
|
+
return this.layers.find((layer) => layer.key === key)?.persistence;
|
|
79
|
+
}
|
|
68
80
|
}
|
|
69
81
|
exports.FlowsPersistenceRegistryImpl = FlowsPersistenceRegistryImpl;
|
|
70
82
|
//# sourceMappingURL=FlowsPersistenceRegistry.js.map
|
|
@@ -2,17 +2,34 @@ import type { EnvPick } from 'env-struct';
|
|
|
2
2
|
import type { env } from '../../envVars';
|
|
3
3
|
import { PersistencePluginRegistry } from '../PersistencePlugin';
|
|
4
4
|
import type { SuitesPersistence } from './SuitesPersistence';
|
|
5
|
+
/**
|
|
6
|
+
* A persistence layer paired with its `PERSISTENCE_PRIORITY` key. See
|
|
7
|
+
* {@link FlowsPersistenceLayer} for why keys must align across registries.
|
|
8
|
+
*/
|
|
9
|
+
export interface SuitesPersistenceLayer {
|
|
10
|
+
key: string;
|
|
11
|
+
persistence: SuitesPersistence;
|
|
12
|
+
}
|
|
5
13
|
export interface SuitesPersistenceRegistry {
|
|
6
14
|
/** Returns the primary persistence layer. */
|
|
7
15
|
get(): Promise<SuitesPersistence>;
|
|
8
16
|
/** Returns all valid persistence layers. Guaranteed to be non-empty. */
|
|
9
17
|
getAll(): Promise<SuitesPersistence[]>;
|
|
18
|
+
/** Returns all valid layers paired with their `PERSISTENCE_PRIORITY` key. */
|
|
19
|
+
getEntries(): Promise<SuitesPersistenceLayer[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the persistence layer registered under the given
|
|
22
|
+
* `PERSISTENCE_PRIORITY` key, or undefined if no such layer exists.
|
|
23
|
+
*/
|
|
24
|
+
getByKey(key: string): Promise<SuitesPersistence | undefined>;
|
|
10
25
|
}
|
|
11
26
|
export declare class SuitesPersistenceRegistryImpl implements SuitesPersistenceRegistry {
|
|
12
27
|
private readonly layers;
|
|
13
|
-
constructor(layers:
|
|
28
|
+
constructor(layers: SuitesPersistenceLayer[]);
|
|
14
29
|
static fromEnvironment(environ: EnvPick<typeof env, 'DONOBU_API_BASE_URL' | 'DONOBU_API_KEY' | 'PERSISTENCE_PRIORITY'>, persistencePlugins?: PersistencePluginRegistry): Promise<SuitesPersistenceRegistryImpl>;
|
|
15
30
|
get(): Promise<SuitesPersistence>;
|
|
16
31
|
getAll(): Promise<SuitesPersistence[]>;
|
|
32
|
+
getEntries(): Promise<SuitesPersistenceLayer[]>;
|
|
33
|
+
getByKey(key: string): Promise<SuitesPersistence | undefined>;
|
|
17
34
|
}
|
|
18
35
|
//# sourceMappingURL=SuitesPersistenceRegistry.d.ts.map
|
|
@@ -21,21 +21,27 @@ class SuitesPersistenceRegistryImpl {
|
|
|
21
21
|
switch (key) {
|
|
22
22
|
case 'DONOBU':
|
|
23
23
|
if (donobuApiKey && donobuApiBaseUrl) {
|
|
24
|
-
layers.push(
|
|
24
|
+
layers.push({
|
|
25
|
+
key,
|
|
26
|
+
persistence: new SuitesPersistenceDonobuApi_1.SuitesPersistenceDonobuApi(donobuApiBaseUrl, donobuApiKey),
|
|
27
|
+
});
|
|
25
28
|
}
|
|
26
29
|
break;
|
|
27
30
|
case 'LOCAL':
|
|
28
|
-
layers.push(
|
|
31
|
+
layers.push({
|
|
32
|
+
key,
|
|
33
|
+
persistence: await SuitesPersistenceSqlite_1.SuitesPersistenceSqlite.create(await (0, DonobuSqliteDb_1.getDonobuSqliteDatabase)()),
|
|
34
|
+
});
|
|
29
35
|
break;
|
|
30
36
|
case 'RAM':
|
|
31
|
-
layers.push(new SuitesPersistenceVolatile_1.SuitesPersistenceVolatile());
|
|
37
|
+
layers.push({ key, persistence: new SuitesPersistenceVolatile_1.SuitesPersistenceVolatile() });
|
|
32
38
|
break;
|
|
33
39
|
default: {
|
|
34
40
|
const plugin = persistencePlugins.get(key);
|
|
35
41
|
if (plugin?.createSuitesPersistence) {
|
|
36
42
|
const impl = await plugin.createSuitesPersistence();
|
|
37
43
|
if (impl) {
|
|
38
|
-
layers.push(impl);
|
|
44
|
+
layers.push({ key, persistence: impl });
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
break;
|
|
@@ -45,11 +51,17 @@ class SuitesPersistenceRegistryImpl {
|
|
|
45
51
|
return new SuitesPersistenceRegistryImpl(layers);
|
|
46
52
|
}
|
|
47
53
|
async get() {
|
|
48
|
-
return this.layers[0];
|
|
54
|
+
return this.layers[0].persistence;
|
|
49
55
|
}
|
|
50
56
|
async getAll() {
|
|
57
|
+
return this.layers.map((layer) => layer.persistence);
|
|
58
|
+
}
|
|
59
|
+
async getEntries() {
|
|
51
60
|
return this.layers;
|
|
52
61
|
}
|
|
62
|
+
async getByKey(key) {
|
|
63
|
+
return this.layers.find((layer) => layer.key === key)?.persistence;
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
66
|
exports.SuitesPersistenceRegistryImpl = SuitesPersistenceRegistryImpl;
|
|
55
67
|
//# sourceMappingURL=SuitesPersistenceRegistry.js.map
|
|
@@ -3,15 +3,30 @@ import type { env } from '../../envVars';
|
|
|
3
3
|
import type { FlowsPersistenceRegistry } from '../flows/FlowsPersistenceRegistry';
|
|
4
4
|
import { PersistencePluginRegistry } from '../PersistencePlugin';
|
|
5
5
|
import type { TestsPersistence } from './TestsPersistence';
|
|
6
|
+
/**
|
|
7
|
+
* A persistence layer paired with its `PERSISTENCE_PRIORITY` key. See
|
|
8
|
+
* {@link FlowsPersistenceLayer} for why keys must align across registries.
|
|
9
|
+
*/
|
|
10
|
+
export interface TestsPersistenceLayer {
|
|
11
|
+
key: string;
|
|
12
|
+
persistence: TestsPersistence;
|
|
13
|
+
}
|
|
6
14
|
export interface TestsPersistenceRegistry {
|
|
7
15
|
/** Returns the primary persistence layer. */
|
|
8
16
|
get(): Promise<TestsPersistence>;
|
|
9
17
|
/** Returns all valid persistence layers. Guaranteed to be non-empty. */
|
|
10
18
|
getAll(): Promise<TestsPersistence[]>;
|
|
19
|
+
/** Returns all valid layers paired with their `PERSISTENCE_PRIORITY` key. */
|
|
20
|
+
getEntries(): Promise<TestsPersistenceLayer[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the persistence layer registered under the given
|
|
23
|
+
* `PERSISTENCE_PRIORITY` key, or undefined if no such layer exists.
|
|
24
|
+
*/
|
|
25
|
+
getByKey(key: string): Promise<TestsPersistence | undefined>;
|
|
11
26
|
}
|
|
12
27
|
export declare class TestsPersistenceRegistryImpl implements TestsPersistenceRegistry {
|
|
13
28
|
private readonly layers;
|
|
14
|
-
constructor(layers:
|
|
29
|
+
constructor(layers: TestsPersistenceLayer[]);
|
|
15
30
|
static fromEnvironment(environ: EnvPick<typeof env, 'DONOBU_API_BASE_URL' | 'DONOBU_API_KEY' | 'PERSISTENCE_PRIORITY'>,
|
|
16
31
|
/**
|
|
17
32
|
* Flows registry — used by the volatile tests layer to compute
|
|
@@ -22,5 +37,7 @@ export declare class TestsPersistenceRegistryImpl implements TestsPersistenceReg
|
|
|
22
37
|
flowsRegistry?: FlowsPersistenceRegistry, persistencePlugins?: PersistencePluginRegistry): Promise<TestsPersistenceRegistryImpl>;
|
|
23
38
|
get(): Promise<TestsPersistence>;
|
|
24
39
|
getAll(): Promise<TestsPersistence[]>;
|
|
40
|
+
getEntries(): Promise<TestsPersistenceLayer[]>;
|
|
41
|
+
getByKey(key: string): Promise<TestsPersistence | undefined>;
|
|
25
42
|
}
|
|
26
43
|
//# sourceMappingURL=TestsPersistenceRegistry.d.ts.map
|
|
@@ -28,21 +28,30 @@ class TestsPersistenceRegistryImpl {
|
|
|
28
28
|
switch (key) {
|
|
29
29
|
case 'DONOBU':
|
|
30
30
|
if (donobuApiKey && donobuApiBaseUrl) {
|
|
31
|
-
layers.push(
|
|
31
|
+
layers.push({
|
|
32
|
+
key,
|
|
33
|
+
persistence: new TestsPersistenceDonobuApi_1.TestsPersistenceDonobuApi(donobuApiBaseUrl, donobuApiKey),
|
|
34
|
+
});
|
|
32
35
|
}
|
|
33
36
|
break;
|
|
34
37
|
case 'LOCAL':
|
|
35
|
-
layers.push(
|
|
38
|
+
layers.push({
|
|
39
|
+
key,
|
|
40
|
+
persistence: await TestsPersistenceSqlite_1.TestsPersistenceSqlite.create(await (0, DonobuSqliteDb_1.getDonobuSqliteDatabase)()),
|
|
41
|
+
});
|
|
36
42
|
break;
|
|
37
43
|
case 'RAM':
|
|
38
|
-
layers.push(
|
|
44
|
+
layers.push({
|
|
45
|
+
key,
|
|
46
|
+
persistence: new TestsPersistenceVolatile_1.TestsPersistenceVolatile(undefined, flowsRegistry ? () => flowsRegistry.get() : undefined),
|
|
47
|
+
});
|
|
39
48
|
break;
|
|
40
49
|
default: {
|
|
41
50
|
const plugin = persistencePlugins.get(key);
|
|
42
51
|
if (plugin?.createTestsPersistence) {
|
|
43
52
|
const impl = await plugin.createTestsPersistence();
|
|
44
53
|
if (impl) {
|
|
45
|
-
layers.push(impl);
|
|
54
|
+
layers.push({ key, persistence: impl });
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
break;
|
|
@@ -52,11 +61,17 @@ class TestsPersistenceRegistryImpl {
|
|
|
52
61
|
return new TestsPersistenceRegistryImpl(layers);
|
|
53
62
|
}
|
|
54
63
|
async get() {
|
|
55
|
-
return this.layers[0];
|
|
64
|
+
return this.layers[0].persistence;
|
|
56
65
|
}
|
|
57
66
|
async getAll() {
|
|
67
|
+
return this.layers.map((layer) => layer.persistence);
|
|
68
|
+
}
|
|
69
|
+
async getEntries() {
|
|
58
70
|
return this.layers;
|
|
59
71
|
}
|
|
72
|
+
async getByKey(key) {
|
|
73
|
+
return this.layers.find((layer) => layer.key === key)?.persistence;
|
|
74
|
+
}
|
|
60
75
|
}
|
|
61
76
|
exports.TestsPersistenceRegistryImpl = TestsPersistenceRegistryImpl;
|
|
62
77
|
//# sourceMappingURL=TestsPersistenceRegistry.js.map
|
|
@@ -74,7 +74,7 @@ current page or element, of which may contain relevant information.`, exports.Sc
|
|
|
74
74
|
return beforeY !== afterY || beforeX !== afterX;
|
|
75
75
|
}, parameters);
|
|
76
76
|
return didScroll
|
|
77
|
-
? `Scrolled ${parameters.direction} ${parameters.maxScroll ? '
|
|
77
|
+
? `Scrolled ${parameters.direction} ${parameters.maxScroll ? '(maximum amount scrolled)' : ''} for `
|
|
78
78
|
: `Nothing to scroll ${parameters.direction}`;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -22,6 +22,7 @@ import type { RunConfig } from '../models/RunConfig';
|
|
|
22
22
|
import type { RunMode } from '../models/RunMode';
|
|
23
23
|
import type { ToolCall } from '../models/ToolCall';
|
|
24
24
|
import type { FlowsPersistenceRegistry } from '../persistence/flows/FlowsPersistenceRegistry';
|
|
25
|
+
import type { TestsPersistenceRegistry } from '../persistence/tests/TestsPersistenceRegistry';
|
|
25
26
|
import type { TargetRuntimePluginRegistry } from '../targets/TargetRuntimePlugin';
|
|
26
27
|
import type { Tool } from '../tools/Tool';
|
|
27
28
|
import type { FlowLogSnapshot } from '../utils/FlowLogBuffer';
|
|
@@ -45,17 +46,22 @@ export declare class DonobuFlowsManager {
|
|
|
45
46
|
private readonly environ;
|
|
46
47
|
private readonly toolRegistry;
|
|
47
48
|
private readonly targetRuntimePlugins;
|
|
49
|
+
private readonly testsPersistenceRegistry;
|
|
48
50
|
static readonly DEFAULT_MESSAGE_DURATION = 2247;
|
|
49
51
|
static readonly DEFAULT_MAX_TOOL_CALLS = 50;
|
|
50
52
|
static readonly DEFAULT_BROWSER_STATE_FILENAME = "browserstate.json";
|
|
51
53
|
static readonly FLOW_NAME_MAX_LENGTH = 255;
|
|
52
54
|
private readonly flowRuntime;
|
|
53
55
|
private readonly flowCatalog;
|
|
54
|
-
constructor(deploymentEnvironment: DonobuDeploymentEnvironment, gptClientFactory: GptClientFactory, gptConfigsManager: GptConfigsManager, agentsManager: AgentsManager, flowsPersistenceRegistry: FlowsPersistenceRegistry, envDataManager: EnvDataManager, controlPanelFactory: ControlPanelFactory, environ: EnvPick<typeof env, 'ANTHROPIC_API_KEY' | 'ANTHROPIC_MODEL_NAME' | 'AWS_ACCESS_KEY_ID' | 'AWS_BEDROCK_MODEL_NAME' | 'AWS_SECRET_ACCESS_KEY' | 'BASE64_GPT_CONFIG' | 'BROWSERBASE_API_KEY' | 'BROWSERBASE_PROJECT_ID' | 'DONOBU_API_KEY' | 'GOOGLE_GENERATIVE_AI_API_KEY' | 'GOOGLE_GENERATIVE_AI_MODEL_NAME' | 'OLLAMA_API_URL' | 'OLLAMA_MODEL_NAME' | 'OPENAI_API_KEY' | 'OPENAI_API_MODEL_NAME'>, toolRegistry: ToolRegistry, targetRuntimePlugins: TargetRuntimePluginRegistry);
|
|
56
|
+
constructor(deploymentEnvironment: DonobuDeploymentEnvironment, gptClientFactory: GptClientFactory, gptConfigsManager: GptConfigsManager, agentsManager: AgentsManager, flowsPersistenceRegistry: FlowsPersistenceRegistry, envDataManager: EnvDataManager, controlPanelFactory: ControlPanelFactory, environ: EnvPick<typeof env, 'ANTHROPIC_API_KEY' | 'ANTHROPIC_MODEL_NAME' | 'AWS_ACCESS_KEY_ID' | 'AWS_BEDROCK_MODEL_NAME' | 'AWS_SECRET_ACCESS_KEY' | 'BASE64_GPT_CONFIG' | 'BROWSERBASE_API_KEY' | 'BROWSERBASE_PROJECT_ID' | 'DONOBU_API_KEY' | 'GOOGLE_GENERATIVE_AI_API_KEY' | 'GOOGLE_GENERATIVE_AI_MODEL_NAME' | 'OLLAMA_API_URL' | 'OLLAMA_MODEL_NAME' | 'OPENAI_API_KEY' | 'OPENAI_API_MODEL_NAME'>, toolRegistry: ToolRegistry, targetRuntimePlugins: TargetRuntimePluginRegistry, testsPersistenceRegistry: TestsPersistenceRegistry);
|
|
55
57
|
/**
|
|
56
58
|
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
57
59
|
* method, adding it to list of active flows.
|
|
58
60
|
*
|
|
61
|
+
* If `flowParams.testId` is set, the new flow is persisted to the same
|
|
62
|
+
* layer as the referenced test so the `flow_metadata.test_id` foreign
|
|
63
|
+
* key holds. Otherwise the primary layer is used.
|
|
64
|
+
*
|
|
59
65
|
* @param gptClient If present, will use this as the associated GPT client for
|
|
60
66
|
* this flow instead of instantiating a new one. If so, the
|
|
61
67
|
* gptConfigNameOverride field will be ignored.
|
|
@@ -168,6 +174,20 @@ export declare class DonobuFlowsManager {
|
|
|
168
174
|
* {@link BrowserStateNotFoundException} if it is not found.
|
|
169
175
|
*/
|
|
170
176
|
getBrowserStorageState(browserStateRef: BrowserStateReference): Promise<BrowserStorageState>;
|
|
177
|
+
/**
|
|
178
|
+
* Picks the flows persistence layer to use when creating a new flow.
|
|
179
|
+
*
|
|
180
|
+
* - If `testId` is null/undefined: use the primary flows layer.
|
|
181
|
+
* - If `testId` is set: look up the test's layer key and use the matching
|
|
182
|
+
* flows layer. If no flows layer matches the test's key (rare —
|
|
183
|
+
* asymmetric registry config), fall back to the primary layer; the FK
|
|
184
|
+
* won't hold but the flow is at least persisted.
|
|
185
|
+
* - If `testId` is set but no test exists with that ID anywhere: fall
|
|
186
|
+
* back to the primary layer (the SQLite FK will reject if applicable;
|
|
187
|
+
* non-DB layers will accept the dangling reference).
|
|
188
|
+
*/
|
|
189
|
+
private resolveLayerForFlowCreate;
|
|
190
|
+
private findTestLayerKey;
|
|
171
191
|
private isLocallyRunning;
|
|
172
192
|
}
|
|
173
193
|
/**
|
|
@@ -47,6 +47,7 @@ const BrowserStateNotFoundException_1 = require("../exceptions/BrowserStateNotFo
|
|
|
47
47
|
const CannotDeleteRunningFlowException_1 = require("../exceptions/CannotDeleteRunningFlowException");
|
|
48
48
|
const FlowNotFoundException_1 = require("../exceptions/FlowNotFoundException");
|
|
49
49
|
const InvalidParamValueException_1 = require("../exceptions/InvalidParamValueException");
|
|
50
|
+
const TestNotFoundException_1 = require("../exceptions/TestNotFoundException");
|
|
50
51
|
const ToolRequiresGptException_1 = require("../exceptions/ToolRequiresGptException");
|
|
51
52
|
const UnknownToolException_1 = require("../exceptions/UnknownToolException");
|
|
52
53
|
const GptConfig_1 = require("../models/GptConfig");
|
|
@@ -64,7 +65,7 @@ const FlowRuntime_1 = require("./FlowRuntime");
|
|
|
64
65
|
const InteractionVisualizer_1 = require("./InteractionVisualizer");
|
|
65
66
|
const ToolManager_1 = require("./ToolManager");
|
|
66
67
|
class DonobuFlowsManager {
|
|
67
|
-
constructor(deploymentEnvironment, gptClientFactory, gptConfigsManager, agentsManager, flowsPersistenceRegistry, envDataManager, controlPanelFactory, environ, toolRegistry, targetRuntimePlugins) {
|
|
68
|
+
constructor(deploymentEnvironment, gptClientFactory, gptConfigsManager, agentsManager, flowsPersistenceRegistry, envDataManager, controlPanelFactory, environ, toolRegistry, targetRuntimePlugins, testsPersistenceRegistry) {
|
|
68
69
|
this.deploymentEnvironment = deploymentEnvironment;
|
|
69
70
|
this.gptClientFactory = gptClientFactory;
|
|
70
71
|
this.gptConfigsManager = gptConfigsManager;
|
|
@@ -75,6 +76,7 @@ class DonobuFlowsManager {
|
|
|
75
76
|
this.environ = environ;
|
|
76
77
|
this.toolRegistry = toolRegistry;
|
|
77
78
|
this.targetRuntimePlugins = targetRuntimePlugins;
|
|
79
|
+
this.testsPersistenceRegistry = testsPersistenceRegistry;
|
|
78
80
|
this.flowRuntime = new FlowRuntime_1.FlowRuntime();
|
|
79
81
|
this.flowCatalog = new FlowCatalog_1.FlowCatalog(this.flowsPersistenceRegistry, this.flowRuntime, this.deploymentEnvironment);
|
|
80
82
|
}
|
|
@@ -82,6 +84,10 @@ class DonobuFlowsManager {
|
|
|
82
84
|
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
|
|
83
85
|
* method, adding it to list of active flows.
|
|
84
86
|
*
|
|
87
|
+
* If `flowParams.testId` is set, the new flow is persisted to the same
|
|
88
|
+
* layer as the referenced test so the `flow_metadata.test_id` foreign
|
|
89
|
+
* key holds. Otherwise the primary layer is used.
|
|
90
|
+
*
|
|
85
91
|
* @param gptClient If present, will use this as the associated GPT client for
|
|
86
92
|
* this flow instead of instantiating a new one. If so, the
|
|
87
93
|
* gptConfigNameOverride field will be ignored.
|
|
@@ -179,7 +185,7 @@ class DonobuFlowsManager {
|
|
|
179
185
|
...targetRuntime.getMetadataFields(),
|
|
180
186
|
provenance: (0, buildProvenance_1.buildProvenance)('DONOBU_STUDIO'),
|
|
181
187
|
};
|
|
182
|
-
const flowsPersistence = await this.
|
|
188
|
+
const flowsPersistence = await this.resolveLayerForFlowCreate(flowParams.testId ?? null);
|
|
183
189
|
const envData = await this.envDataManager.getByNames(flowMetadata.envVars ?? []);
|
|
184
190
|
const donobuFlow = new DonobuFlow_1.DonobuFlow(this, envData, flowsPersistence, gptClientData.gptClient, toolManager, interactionVisualizer, toolCallsOnStart, [], [], targetRuntime.inspector, flowMetadata, targetRuntime.controlPanel);
|
|
185
191
|
await flowsPersistence.setFlowMetadata(flowMetadata);
|
|
@@ -652,6 +658,49 @@ class DonobuFlowsManager {
|
|
|
652
658
|
}
|
|
653
659
|
throw new BrowserStateNotFoundException_1.BrowserStateNotFoundException(flowMetadata.id);
|
|
654
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Picks the flows persistence layer to use when creating a new flow.
|
|
663
|
+
*
|
|
664
|
+
* - If `testId` is null/undefined: use the primary flows layer.
|
|
665
|
+
* - If `testId` is set: look up the test's layer key and use the matching
|
|
666
|
+
* flows layer. If no flows layer matches the test's key (rare —
|
|
667
|
+
* asymmetric registry config), fall back to the primary layer; the FK
|
|
668
|
+
* won't hold but the flow is at least persisted.
|
|
669
|
+
* - If `testId` is set but no test exists with that ID anywhere: fall
|
|
670
|
+
* back to the primary layer (the SQLite FK will reject if applicable;
|
|
671
|
+
* non-DB layers will accept the dangling reference).
|
|
672
|
+
*/
|
|
673
|
+
async resolveLayerForFlowCreate(testId) {
|
|
674
|
+
if (!testId) {
|
|
675
|
+
return this.flowsPersistenceRegistry.get();
|
|
676
|
+
}
|
|
677
|
+
let testLayerKey;
|
|
678
|
+
try {
|
|
679
|
+
testLayerKey = await this.findTestLayerKey(testId);
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
if (!(error instanceof TestNotFoundException_1.TestNotFoundException)) {
|
|
683
|
+
throw error;
|
|
684
|
+
}
|
|
685
|
+
return this.flowsPersistenceRegistry.get();
|
|
686
|
+
}
|
|
687
|
+
const matched = await this.flowsPersistenceRegistry.getByKey(testLayerKey);
|
|
688
|
+
return matched ?? (await this.flowsPersistenceRegistry.get());
|
|
689
|
+
}
|
|
690
|
+
async findTestLayerKey(testId) {
|
|
691
|
+
for (const { key, persistence, } of await this.testsPersistenceRegistry.getEntries()) {
|
|
692
|
+
try {
|
|
693
|
+
await persistence.getTestById(testId);
|
|
694
|
+
return key;
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
if (!(error instanceof TestNotFoundException_1.TestNotFoundException)) {
|
|
698
|
+
throw error;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
throw TestNotFoundException_1.TestNotFoundException.forId(testId);
|
|
703
|
+
}
|
|
655
704
|
isLocallyRunning() {
|
|
656
705
|
return this.deploymentEnvironment === 'LOCAL';
|
|
657
706
|
}
|
|
@@ -46,12 +46,12 @@ async function setupDonobuStack(donobuDeploymentEnvironment, controlPanelFactory
|
|
|
46
46
|
const agentsPersistence = await AgentsPersistenceSqlite_1.AgentsPersistenceSqlite.create(sqliteDb);
|
|
47
47
|
const agentsManager = await AgentsManager_1.AgentsManager.create(agentsPersistence, gptConfigsManager);
|
|
48
48
|
const flowsPersistenceRegistry = await FlowsPersistenceRegistry_1.FlowsPersistenceRegistryImpl.fromEnvironment(environ, persistencePlugins);
|
|
49
|
-
const envPersistenceFactory = await EnvPersistenceRegistry_1.EnvPersistenceRegistryImpl.fromEnvironment(envPersistenceVolatile ?? null, environ, persistencePlugins);
|
|
50
|
-
const envDataManager = new EnvDataManager_1.EnvDataManager(envPersistenceFactory);
|
|
51
|
-
const flowsManager = new DonobuFlowsManager_1.DonobuFlowsManager(donobuDeploymentEnvironment, gptClientFactory, gptConfigsManager, agentsManager, flowsPersistenceRegistry, envDataManager, controlPanelFactory, environ, resolvedToolRegistry, targetRuntimePlugins);
|
|
52
49
|
const testsPersistenceRegistry = await TestsPersistenceRegistry_1.TestsPersistenceRegistryImpl.fromEnvironment(environ, flowsPersistenceRegistry);
|
|
53
|
-
const testsManager = new TestsManager_1.TestsManager(testsPersistenceRegistry, flowsManager);
|
|
54
50
|
const suitesPersistenceRegistry = await SuitesPersistenceRegistry_1.SuitesPersistenceRegistryImpl.fromEnvironment(environ);
|
|
51
|
+
const envPersistenceFactory = await EnvPersistenceRegistry_1.EnvPersistenceRegistryImpl.fromEnvironment(envPersistenceVolatile ?? null, environ, persistencePlugins);
|
|
52
|
+
const envDataManager = new EnvDataManager_1.EnvDataManager(envPersistenceFactory);
|
|
53
|
+
const flowsManager = new DonobuFlowsManager_1.DonobuFlowsManager(donobuDeploymentEnvironment, gptClientFactory, gptConfigsManager, agentsManager, flowsPersistenceRegistry, envDataManager, controlPanelFactory, environ, resolvedToolRegistry, targetRuntimePlugins, testsPersistenceRegistry);
|
|
54
|
+
const testsManager = new TestsManager_1.TestsManager(testsPersistenceRegistry, suitesPersistenceRegistry, flowsManager);
|
|
55
55
|
const suitesManager = new SuitesManager_1.SuitesManager(suitesPersistenceRegistry, testsPersistenceRegistry);
|
|
56
56
|
return {
|
|
57
57
|
toolRegistry: resolvedToolRegistry,
|
|
@@ -1,31 +1,61 @@
|
|
|
1
1
|
import type { PaginatedResult } from '../models/PaginatedResult';
|
|
2
|
+
/**
|
|
3
|
+
* Per-layer pagination state, carried across pages inside the composite
|
|
4
|
+
* page token. `cursor` is the layer's own opaque page token; `resumeAfterId`
|
|
5
|
+
* is the id of the last item from this layer kept on the previous page (in
|
|
6
|
+
* the layer's own order). `exhausted` becomes true once a layer has nothing
|
|
7
|
+
* more to give and we've already returned everything it contributed.
|
|
8
|
+
*/
|
|
9
|
+
interface SourceState {
|
|
10
|
+
cursor?: string;
|
|
11
|
+
resumeAfterId?: string;
|
|
12
|
+
exhausted: boolean;
|
|
13
|
+
}
|
|
2
14
|
/**
|
|
3
15
|
* Internal state tracked across pages when federating results from multiple
|
|
4
|
-
* persistence layers.
|
|
16
|
+
* persistence layers. Serialized as base64(JSON(...)) into the composite
|
|
17
|
+
* page token.
|
|
5
18
|
*/
|
|
6
19
|
interface FederatedPaginationState {
|
|
7
|
-
|
|
8
|
-
exhaustedSources: number[];
|
|
9
|
-
cursorTimestamp: number | null;
|
|
20
|
+
sources: SourceState[];
|
|
10
21
|
}
|
|
11
22
|
export declare function createCompositePageToken(state: FederatedPaginationState): string;
|
|
12
23
|
export declare function parseCompositePageToken(token?: string): FederatedPaginationState;
|
|
13
24
|
/**
|
|
14
|
-
* Federate a paginated listing across multiple persistence layers
|
|
25
|
+
* Federate a paginated listing across multiple persistence layers
|
|
26
|
+
* (resume-by-id).
|
|
27
|
+
*
|
|
28
|
+
* Each layer holds an opaque cursor and an optional `resumeAfterId`. On
|
|
29
|
+
* each page request, federation fetches from each non-exhausted layer
|
|
30
|
+
* (advancing through batches as needed until it has at least `limit`
|
|
31
|
+
* candidates per layer), stable-sorts the merged contributions with the
|
|
32
|
+
* caller's `comparator`, and trims to `limit`. For each layer, the
|
|
33
|
+
* **last kept item in that layer's order** becomes the new
|
|
34
|
+
* `resumeAfterId`, with `cursor` set to the layer-token used to fetch
|
|
35
|
+
* that item's batch. Layers whose contribution is wholly dropped by the
|
|
36
|
+
* trim retain their pre-call state so their items remain reachable on
|
|
37
|
+
* later pages.
|
|
15
38
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
39
|
+
* **Critical contract: the comparator must NOT tiebreak by id.** Ties
|
|
40
|
+
* on the sort key must return 0 from `comparator`. `Array.sort` is
|
|
41
|
+
* stable since ES2019, and federation feeds the merge in
|
|
42
|
+
* `[layer0..., layer1..., ...]` insertion order, so ties resolve by
|
|
43
|
+
* `(layerIndex, layer-internal position)` — exactly the property that
|
|
44
|
+
* keeps each layer's kept items as a *prefix* of its own order, which
|
|
45
|
+
* is what makes a single `resumeAfterId` representable. An id
|
|
46
|
+
* tiebreaker would silently break this and lose items.
|
|
19
47
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
48
|
+
* **Layer contract.** Layers must return the same items in the same
|
|
49
|
+
* order for the same `(query, pageToken)` pair across calls. Their
|
|
50
|
+
* internal ordering does not need to match federation's tiebreaker —
|
|
51
|
+
* just be deterministic.
|
|
24
52
|
*/
|
|
25
53
|
export declare function federatedList<TQuery extends {
|
|
26
54
|
limit?: number;
|
|
27
55
|
pageToken?: string;
|
|
28
|
-
}, TItem
|
|
56
|
+
}, TItem extends {
|
|
57
|
+
id: string;
|
|
58
|
+
}>(layers: {
|
|
29
59
|
getItems: (query: TQuery) => Promise<PaginatedResult<TItem>>;
|
|
30
60
|
}[], query: TQuery, comparator: (a: TItem, b: TItem) => number): Promise<PaginatedResult<TItem>>;
|
|
31
61
|
export {};
|