@oomol-lab/open-flow 0.1.0-beta.76 → 0.1.0-beta.77
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/README.md +1 -0
- package/dist/browser/{ContentIcon-Cm1hbVzW.js → ContentIcon-VZQO8VZU.js} +8 -5
- package/dist/browser/{FlowCanvas-NzKtla_c.js → FlowCanvas-BNGDMaWr.js} +15 -3
- package/dist/browser/{createResourceDialog-BgdedQqu.js → createResourceDialog-BmtakfVg.js} +63 -57
- package/dist/browser/{dialog-aSElFFw2.js → dialog-BcFBfRB1.js} +84 -15
- package/dist/browser/{exportSvg-Dcz4HTo_.js → exportSvg-CG8hEUNk.js} +2 -2
- package/dist/browser/{flowChanges-NZyS7HSp.js → flowChanges-BGo_4Ypo.js} +5849 -10882
- package/dist/browser/{flowWorkspace-Bl0SDehy.js → flowWorkspace-BFHGev7s.js} +1025 -2607
- package/dist/browser/preview.css +112 -5
- package/dist/browser/preview.js +1630 -9295
- package/dist/browser/theme.css +4 -2
- package/dist/browser/{tooltip-3XMK9jkr.js → tooltip-BAWSUexx.js} +1 -1
- package/dist/browser/{typeScriptSession-C9sBGOKZ.js → typeScriptSession-CRoezJPN.js} +1 -1
- package/dist/browser/ui.css +111 -4
- package/dist/browser/ui.js +3 -2
- package/dist/browser/workbench.css +9906 -9778
- package/dist/browser/workbench.d.ts +6 -2
- package/dist/browser/workbench.js +838 -499
- package/dist/browser/{workbenchSelect-DBGP7Y4Z.js → workbenchSelect-CjK-xdm3.js} +2 -2
- package/dist/command/index.js +48 -48
- package/dist/common/authoring.d.ts +4 -4
- package/dist/common/authoringExamples.d.ts +3 -2
- package/dist/common/authoringSchema.d.ts +23 -4
- package/dist/common/control-api-conformance.js +162 -15
- package/dist/common/control-api-errors.d.ts +12 -0
- package/dist/common/control-api.d.ts +35 -2
- package/dist/common/control-api.js +187 -37
- package/dist/common/control-requests.d.ts +22 -3
- package/dist/common/control-requests.js +152 -14
- package/dist/common/engine-contract.d.ts +3 -2
- package/dist/common/flow-authoring.js +55 -7
- package/dist/common/flow-change-schema.d.ts +1 -1
- package/dist/common/flow-change.d.ts +2 -2
- package/dist/common/flow-change.js +56 -8
- package/dist/common/flow-encoding.js +55 -7
- package/dist/common/flow-semantics.js +91 -7
- package/dist/common/flow-state.d.ts +123 -0
- package/dist/common/flow-state.js +5852 -0
- package/dist/common/mcp.d.ts +96 -3
- package/dist/common/mcp.js +165 -13
- package/dist/common/run-events.js +1 -0
- package/dist/common/runtime-contract.d.ts +14 -0
- package/dist/common/runtime-contract.js +118 -5
- package/dist/common/scheduler-node-execution.d.ts +1 -0
- package/dist/common/scheduler.d.ts +1 -1
- package/dist/common/scheduler.js +52 -3
- package/dist/common/state-conformance.d.ts +16 -0
- package/dist/common/tags.d.ts +4 -0
- package/dist/index.d.ts +11 -0
- package/package.json +5 -1
- package/src/control/common/api.ts +84 -11
- package/src/control/common/authoring.ts +6 -1
- package/src/control/common/authoringExamples.ts +8 -0
- package/src/control/common/authoringSchema.ts +18 -12
- package/src/control/common/errors.ts +6 -0
- package/src/control/common/flowDecoders.ts +1 -0
- package/src/control/common/mcp.ts +16 -0
- package/src/control/common/requests.ts +10 -2
- package/src/control/common/tags.ts +20 -0
- package/src/execution/common/events.ts +3 -0
- package/src/execution/common/node-execution.ts +3 -0
- package/src/execution/common/scheduler.ts +2 -0
- package/src/flow/common/change.ts +2 -1
- package/src/flow/common/changeSchema.ts +11 -1
- package/src/flow/common/errorHandling.ts +1 -1
- package/src/flow/common/semantics.ts +20 -0
- package/src/runtime/common/engineContract.ts +6 -3
- package/src/runtime/common/runtime.ts +116 -0
- package/src/state/common/conformance.ts +86 -0
- package/src/state/common/state.ts +169 -0
- package/src/types/index.ts +11 -0
package/dist/common/scheduler.js
CHANGED
|
@@ -9535,6 +9535,51 @@ var httpRequestExecutorSchema = strictObject({
|
|
|
9535
9535
|
]).default("auto"),
|
|
9536
9536
|
statusPolicy: _enum(["success", "all"]).default("success")
|
|
9537
9537
|
}).extend({ kind: literal("http-request") });
|
|
9538
|
+
strictObject({
|
|
9539
|
+
scope: _enum(["draft", "live"]),
|
|
9540
|
+
values: boolean().optional()
|
|
9541
|
+
});
|
|
9542
|
+
strictObject({
|
|
9543
|
+
scope: _enum(["draft", "live"]),
|
|
9544
|
+
key: string$1().min(1).refine((key) => new TextEncoder().encode(key).length <= 256).optional()
|
|
9545
|
+
});
|
|
9546
|
+
strictObject({
|
|
9547
|
+
scope: _enum(["draft", "live"]),
|
|
9548
|
+
space: string$1().regex(/^(flow|node:.+)$/)
|
|
9549
|
+
});
|
|
9550
|
+
var dedupeExecutorSchema = strictObject({
|
|
9551
|
+
field: string$1().default(""),
|
|
9552
|
+
mode: _enum(["new", "increasing"]).default("new"),
|
|
9553
|
+
space: _enum(["node", "flow"]).default("node")
|
|
9554
|
+
}).extend({ kind: literal("dedupe") });
|
|
9555
|
+
object({
|
|
9556
|
+
version: literal(1),
|
|
9557
|
+
scope: _enum(["draft", "live"]),
|
|
9558
|
+
limits: object({
|
|
9559
|
+
maxValueBytes: number().positive(),
|
|
9560
|
+
maxValues: number().int().positive(),
|
|
9561
|
+
maxSpaces: number().int().positive(),
|
|
9562
|
+
maxIdentities: number().positive(),
|
|
9563
|
+
maxRunWrites: number().positive()
|
|
9564
|
+
}),
|
|
9565
|
+
values: array(object({
|
|
9566
|
+
key: string$1(),
|
|
9567
|
+
size: number().nonnegative(),
|
|
9568
|
+
updatedAt: number(),
|
|
9569
|
+
value: json$1().optional()
|
|
9570
|
+
})),
|
|
9571
|
+
spaces: array(object({
|
|
9572
|
+
space: string$1(),
|
|
9573
|
+
mode: _enum(["new", "increasing"]),
|
|
9574
|
+
count: number().nonnegative(),
|
|
9575
|
+
watermark: union([
|
|
9576
|
+
string$1(),
|
|
9577
|
+
number(),
|
|
9578
|
+
_null()
|
|
9579
|
+
]),
|
|
9580
|
+
updatedAt: number()
|
|
9581
|
+
}))
|
|
9582
|
+
});
|
|
9538
9583
|
//#endregion
|
|
9539
9584
|
//#region src/flow/common/retry.ts
|
|
9540
9585
|
var retryNodeTypes = [
|
|
@@ -9889,7 +9934,8 @@ var managedFields = object({
|
|
|
9889
9934
|
flowId: text,
|
|
9890
9935
|
revisionId: text
|
|
9891
9936
|
}),
|
|
9892
|
-
httpRequestExecutorSchema
|
|
9937
|
+
httpRequestExecutorSchema,
|
|
9938
|
+
dedupeExecutorSchema
|
|
9893
9939
|
])
|
|
9894
9940
|
});
|
|
9895
9941
|
var managed = managedFields.transform(limitDecisionTask);
|
|
@@ -10143,7 +10189,8 @@ object({
|
|
|
10143
10189
|
literal(9),
|
|
10144
10190
|
literal(10),
|
|
10145
10191
|
literal(11),
|
|
10146
|
-
literal(12)
|
|
10192
|
+
literal(12),
|
|
10193
|
+
literal(13)
|
|
10147
10194
|
]),
|
|
10148
10195
|
document,
|
|
10149
10196
|
modules: record$1(text, module)
|
|
@@ -10551,7 +10598,7 @@ var nodeErrorSchema = {
|
|
|
10551
10598
|
}
|
|
10552
10599
|
};
|
|
10553
10600
|
function supportsNodeErrorHandling(node) {
|
|
10554
|
-
return supportsNodeRetry(node);
|
|
10601
|
+
return supportsNodeRetry(node) || node.kind === "task" && "executor" in node.task && node.task.executor.kind === "dedupe";
|
|
10555
10602
|
}
|
|
10556
10603
|
function nodeSystemOutputPorts(node) {
|
|
10557
10604
|
return supportsNodeErrorHandling(node) && node.onError == "error" ? { error: {
|
|
@@ -11580,6 +11627,7 @@ function executeNode(context, input) {
|
|
|
11580
11627
|
if (additional.has(handle)) return false;
|
|
11581
11628
|
const task = "executor" in node.task ? node.task : void 0;
|
|
11582
11629
|
if (task == null || !["openapi", "http-request"].includes(task.executor.kind)) return true;
|
|
11630
|
+
if (Object.hasOwn(input.launchInputs ?? {}, handle)) return true;
|
|
11583
11631
|
const mapping = node.inputs[handle];
|
|
11584
11632
|
return mapping?.kind != "unset" && (mapping != null || task.inputs.some((port) => "handle" in port && port.handle == handle && port.value !== void 0));
|
|
11585
11633
|
})),
|
|
@@ -11994,6 +12042,7 @@ function runGraph(context, target, runId, launchInputs = {}, trigger, resume) {
|
|
|
11994
12042
|
node,
|
|
11995
12043
|
jobId,
|
|
11996
12044
|
nodeInputs,
|
|
12045
|
+
launchInputs: launch[nodeId],
|
|
11997
12046
|
resolution,
|
|
11998
12047
|
flowId: target.flowId,
|
|
11999
12048
|
runId,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JsonValue } from './flow-model.js';
|
|
2
|
+
import type { DedupeConfig } from './flow-state.js';
|
|
3
|
+
export interface StateConformanceHarness {
|
|
4
|
+
readonly maxIdentities: number;
|
|
5
|
+
/** Executes Code on the same initially empty Flow and scope on every call; returns its `result` output. */
|
|
6
|
+
run(source: string): Promise<JsonValue>;
|
|
7
|
+
/** Executes the same dedupe node across Runs, updating its business config and items. */
|
|
8
|
+
dedupe(config: DedupeConfig, items: JsonValue[]): Promise<{
|
|
9
|
+
items: JsonValue[];
|
|
10
|
+
duplicates: JsonValue[];
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export declare const stateConformanceCases: readonly {
|
|
14
|
+
name: string;
|
|
15
|
+
verify(harness: StateConformanceHarness): Promise<void>;
|
|
16
|
+
}[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FlowQuery, Tag, TagListItem } from './control-api.js';
|
|
2
|
+
export declare function normalizeFlowQuery(query?: FlowQuery): Required<FlowQuery>;
|
|
3
|
+
export declare function decodeTag(value: unknown): Tag;
|
|
4
|
+
export declare function decodeTagListItem(value: unknown): TagListItem;
|
package/dist/index.d.ts
CHANGED
|
@@ -102,6 +102,17 @@ export interface TaskContext<Actions extends object = Record<string, never>, Wor
|
|
|
102
102
|
readonly inputs: Readonly<Record<string, unknown>>;
|
|
103
103
|
readonly runId: string;
|
|
104
104
|
readonly blockId: string;
|
|
105
|
+
readonly state: {
|
|
106
|
+
get(key: string): Promise<JsonValue | null>;
|
|
107
|
+
set(key: string, value: JsonValue): Promise<void>;
|
|
108
|
+
insert(key: string, value: Exclude<JsonValue, null>): Promise<boolean>;
|
|
109
|
+
delete(key: string): Promise<void>;
|
|
110
|
+
list(): Promise<string[]>;
|
|
111
|
+
};
|
|
112
|
+
readonly dedupe: {
|
|
113
|
+
insert(ids: readonly (string | number)[]): Promise<boolean[]>;
|
|
114
|
+
has(ids: readonly (string | number)[]): Promise<boolean[]>;
|
|
115
|
+
};
|
|
105
116
|
readonly artifact: ArtifactCapability;
|
|
106
117
|
/** Reads the selected direct predecessors, sorted by node ID; empty for a graph root. */
|
|
107
118
|
getPrevious(): Promise<readonly PreviousNode[]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oomol-lab/open-flow",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.77",
|
|
4
4
|
"description": "TypeScript contracts and Browser runtime for Open Flow.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
"types": "./dist/common/decision.d.ts",
|
|
26
26
|
"import": "./dist/common/decision.js"
|
|
27
27
|
},
|
|
28
|
+
"./flow-state": {
|
|
29
|
+
"types": "./dist/common/flow-state.d.ts",
|
|
30
|
+
"import": "./dist/common/flow-state.js"
|
|
31
|
+
},
|
|
28
32
|
"./http-request": {
|
|
29
33
|
"types": "./dist/common/http-request.d.ts",
|
|
30
34
|
"import": "./dist/common/http-request.js"
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CanvasCommand } from '../../canvas/common/commands.ts'
|
|
2
|
+
import type { FlowStateView, StateScope } from '../../state/common/state.ts'
|
|
2
3
|
|
|
3
4
|
import { currentEngineContract } from '../../runtime/common/engineContract.ts'
|
|
5
|
+
import { flowStateViewSchema } from '../../state/common/state.ts'
|
|
4
6
|
import { workflowContractSchema, workflowReferencesSchema } from '../../workflow/common/workflow.ts'
|
|
5
7
|
export type { CanvasCommand, CanvasCommandSubscriber } from '../../canvas/common/commands.ts'
|
|
6
8
|
import type { LiveDetails } from './liveDetails.ts'
|
|
@@ -77,6 +79,8 @@ export type { FlowCatalogEvent, FlowChangeEvent } from './flowNotifications.ts'
|
|
|
77
79
|
|
|
78
80
|
import { ApiError } from './errors.ts'
|
|
79
81
|
import { randomId } from './random.ts'
|
|
82
|
+
import { decodeTag, decodeTagListItem, normalizeFlowQuery } from './tags.ts'
|
|
83
|
+
export { normalizeFlowQuery } from './tags.ts'
|
|
80
84
|
|
|
81
85
|
export interface CanvasBroadcast {
|
|
82
86
|
readonly version: 1
|
|
@@ -103,7 +107,29 @@ export interface FlowResources {
|
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
export interface FlowUpdate {
|
|
111
|
+
readonly name?: string
|
|
112
|
+
readonly tagIds?: readonly string[]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface FlowQuery {
|
|
116
|
+
readonly query?: string
|
|
117
|
+
readonly tagIds?: readonly string[]
|
|
118
|
+
readonly untagged?: boolean
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface Tag {
|
|
122
|
+
readonly tagId: string
|
|
123
|
+
readonly name: string
|
|
124
|
+
readonly version: 1
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface TagListItem extends Tag {
|
|
128
|
+
readonly referenceCount: number
|
|
129
|
+
}
|
|
130
|
+
|
|
106
131
|
export interface Flow {
|
|
132
|
+
readonly tagIds?: readonly string[]
|
|
107
133
|
readonly live?: { readonly enabled: boolean; readonly hasStart: boolean; readonly publicationId: string; readonly revisionId: string }
|
|
108
134
|
|
|
109
135
|
readonly createdAt: string
|
|
@@ -677,6 +703,7 @@ export function decodeRunEvent(value: unknown) {
|
|
|
677
703
|
nodeKind !== 'decision' &&
|
|
678
704
|
nodeKind !== 'openapi' &&
|
|
679
705
|
nodeKind !== 'http-request' &&
|
|
706
|
+
nodeKind !== 'dedupe' &&
|
|
680
707
|
nodeKind !== 'llm' &&
|
|
681
708
|
nodeKind !== 'agent' &&
|
|
682
709
|
nodeKind !== 'value' &&
|
|
@@ -809,9 +836,13 @@ export class ControlClient {
|
|
|
809
836
|
}
|
|
810
837
|
|
|
811
838
|
async listFlows(
|
|
812
|
-
options: { readonly cursor?: string; readonly includeTotal?: boolean; readonly limit?: number; readonly signal?: AbortSignal } = {},
|
|
839
|
+
options: FlowQuery & { readonly cursor?: string; readonly includeTotal?: boolean; readonly limit?: number; readonly signal?: AbortSignal } = {},
|
|
813
840
|
): Promise<FlowPage> {
|
|
814
841
|
const parameters = new URLSearchParams()
|
|
842
|
+
const filter = normalizeFlowQuery(options)
|
|
843
|
+
if (filter.query) parameters.set('query', filter.query)
|
|
844
|
+
if (filter.tagIds.length) parameters.set('tagIds', filter.tagIds.join(','))
|
|
845
|
+
if (filter.untagged) parameters.set('untagged', 'true')
|
|
815
846
|
if (options.cursor != null) parameters.set('cursor', options.cursor)
|
|
816
847
|
if (options.limit != null) parameters.set('limit', String(options.limit))
|
|
817
848
|
if (options.includeTotal != null) parameters.set('includeTotal', String(options.includeTotal))
|
|
@@ -897,20 +928,23 @@ export class ControlClient {
|
|
|
897
928
|
}
|
|
898
929
|
}
|
|
899
930
|
|
|
900
|
-
async importFlow(
|
|
931
|
+
async importFlow(
|
|
932
|
+
file: WorkflowFile,
|
|
933
|
+
options: { readonly name?: string; readonly teamId?: string; readonly idempotencyKey?: string; readonly tagIds?: readonly string[] } = {},
|
|
934
|
+
): Promise<Flow> {
|
|
901
935
|
return flow(
|
|
902
936
|
await this.request('/v1/flows/import', {
|
|
903
937
|
method: 'POST',
|
|
904
938
|
headers: { 'Idempotency-Key': options.idempotencyKey ?? `flow-${randomId()}` },
|
|
905
|
-
body: JSON.stringify({ file, name: options.name, teamId: options.teamId, version: 1 }),
|
|
939
|
+
body: JSON.stringify({ file, name: options.name, teamId: options.teamId, tagIds: options.tagIds, version: 1 }),
|
|
906
940
|
}),
|
|
907
941
|
)
|
|
908
942
|
}
|
|
909
943
|
|
|
910
|
-
async createFlow(name: string, idempotencyKey = `flow-${randomId()}`, teamId?: string): Promise<Flow> {
|
|
944
|
+
async createFlow(name: string, idempotencyKey = `flow-${randomId()}`, teamId?: string, tagIds?: readonly string[]): Promise<Flow> {
|
|
911
945
|
return flow(
|
|
912
946
|
await this.request('/v1/flows', {
|
|
913
|
-
body: JSON.stringify({ name, ...(teamId == null ? {} : { teamId }), version: 1 }),
|
|
947
|
+
body: JSON.stringify({ name, ...(teamId == null ? {} : { teamId }), ...(tagIds == null ? {} : { tagIds }), version: 1 }),
|
|
914
948
|
headers: { 'idempotency-key': idempotencyKey },
|
|
915
949
|
method: 'POST',
|
|
916
950
|
}),
|
|
@@ -939,13 +973,52 @@ export class ControlClient {
|
|
|
939
973
|
return source.listeners.map(errorListener)
|
|
940
974
|
}
|
|
941
975
|
|
|
942
|
-
async
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
976
|
+
async listTags(signal?: AbortSignal): Promise<{ readonly tags: readonly TagListItem[]; readonly version: 1 }> {
|
|
977
|
+
const source = record(await this.request('/v1/tags', { signal }))
|
|
978
|
+
if (source.version != 1 || !Array.isArray(source.tags)) return invalidResponse()
|
|
979
|
+
return { tags: source.tags.map(decodeTagListItem), version: 1 }
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
async createTag(name: string): Promise<Tag> {
|
|
983
|
+
return decodeTag(await this.request('/v1/tags', { method: 'POST', body: JSON.stringify({ name, version: 1 }) }))
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
async renameTag(tagId: string, name: string): Promise<Tag> {
|
|
987
|
+
return decodeTag(await this.request(`/v1/tags/${segment(tagId)}`, { method: 'PATCH', body: JSON.stringify({ name, version: 1 }) }))
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
async deleteTag(tagId: string): Promise<void> {
|
|
991
|
+
const source = record(await this.request(`/v1/tags/${segment(tagId)}`, { method: 'DELETE' }))
|
|
992
|
+
if (source.version != 1) invalidResponse()
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
async setFlowTags(flowId: string, tagIds: readonly string[]): Promise<Flow> {
|
|
996
|
+
return flow(await this.request(`/v1/flows/${segment(flowId)}/tags`, { method: 'PUT', body: JSON.stringify({ tagIds, version: 1 }) }))
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async updateFlow(flowId: string, changes: FlowUpdate): Promise<Flow> {
|
|
1000
|
+
return flow(await this.request(`/v1/flows/${segment(flowId)}`, { method: 'PATCH', body: JSON.stringify({ ...changes, version: 1 }) }))
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
async getFlowState(flowId: string, scope: StateScope, values = false): Promise<FlowStateView> {
|
|
1004
|
+
const result = flowStateViewSchema.safeParse(await this.request(`/v1/flows/${segment(flowId)}/state?scope=${scope}&values=${values}`))
|
|
1005
|
+
return result.success && result.data.scope === scope ? result.data : invalidResponse()
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
async clearFlowState(flowId: string, scope: StateScope, key?: string): Promise<void> {
|
|
1009
|
+
const source = record(
|
|
1010
|
+
await this.request(`/v1/flows/${segment(flowId)}/state?scope=${scope}${key == null ? '' : `&key=${encodeURIComponent(key)}`}`, { method: 'DELETE' }),
|
|
948
1011
|
)
|
|
1012
|
+
if (source.version !== 1 || source.cleared !== true) invalidResponse()
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
async clearFlowDedupe(flowId: string, scope: StateScope, space: string): Promise<void> {
|
|
1016
|
+
const source = record(await this.request(`/v1/flows/${segment(flowId)}/dedupe?scope=${scope}&space=${encodeURIComponent(space)}`, { method: 'DELETE' }))
|
|
1017
|
+
if (source.version !== 1 || source.cleared !== true) invalidResponse()
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
async renameFlow(flowId: string, name: string): Promise<Flow> {
|
|
1021
|
+
return this.updateFlow(flowId, { name })
|
|
949
1022
|
}
|
|
950
1023
|
|
|
951
1024
|
async deleteFlow(flowId: string): Promise<Flow> {
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
} from '../../flow/common/nodeChanges.ts'
|
|
44
44
|
import { retryConfigurationSchema } from '../../flow/common/retry.ts'
|
|
45
45
|
import { httpRequestTask, httpRequestConfigSchema, httpRequestInputs } from '../../http/common/httpRequest.ts'
|
|
46
|
+
import { dedupeConfigSchema, dedupeTask } from '../../state/common/state.ts'
|
|
46
47
|
import { withWorkflowTask, workflowTask, workflowSelectionTask, workflowCapability, workflowAgentTool } from '../../workflow/common/workflow.ts'
|
|
47
48
|
import { conditionConfigurationView, prepareConditionConfiguration } from './authoringCondition.ts'
|
|
48
49
|
import { authoringDiagnosticField } from './authoringDiagnosticFields.ts'
|
|
@@ -146,7 +147,7 @@ export function authoringNode(content: RevisionContent, ref: string) {
|
|
|
146
147
|
),
|
|
147
148
|
}
|
|
148
149
|
else if (task.executor.kind == 'workflow') config = { flowId: task.executor.flowId }
|
|
149
|
-
else if (task.executor.kind == 'http-request') {
|
|
150
|
+
else if (task.executor.kind == 'http-request' || task.executor.kind == 'dedupe') {
|
|
150
151
|
const { kind: _kind, ...settings } = task.executor
|
|
151
152
|
config = settings
|
|
152
153
|
} else if (task.executor.kind == 'openapi') {
|
|
@@ -521,6 +522,7 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
521
522
|
task = { ...before, name: String(update.name), executor: { ...executor, ...config } } as ManagedTaskDefinition
|
|
522
523
|
} else task = await connectorTask(config, String(update.name))
|
|
523
524
|
} else if (type == 'decision') task = decisionTask(config.questions as Parameters<typeof decisionTask>[0], String(update.name))
|
|
525
|
+
else if (type == 'dedupe') task = { ...before, name: String(update.name), executor: { kind: 'dedupe', ...dedupeConfigSchema.parse(config) } }
|
|
524
526
|
else if (type == 'http-request') {
|
|
525
527
|
task = httpRequestTask(httpRequestConfigSchema.parse(config), String(update.name), before)
|
|
526
528
|
next = { ...next, inputs: httpRequestInputs(node.inputs, before, task) } as GraphNode
|
|
@@ -774,6 +776,9 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
774
776
|
case 'connector':
|
|
775
777
|
changes = createManagedTask(identity, await connectorTask(config, name))
|
|
776
778
|
break
|
|
779
|
+
case 'dedupe':
|
|
780
|
+
changes = createManagedTask(identity, dedupeTask(dedupeConfigSchema.parse(config), name))
|
|
781
|
+
break
|
|
777
782
|
case 'http-request':
|
|
778
783
|
changes = createManagedTask(identity, httpRequestTask(httpRequestConfigSchema.parse(config), name))
|
|
779
784
|
break
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from '../../flow/common/nodeChanges.ts'
|
|
14
14
|
import { httpRequestTask } from '../../http/common/httpRequest.ts'
|
|
15
15
|
import { openApiTask, selectOperation } from '../../openapi/common/openapi.ts'
|
|
16
|
+
import { dedupeTask } from '../../state/common/state.ts'
|
|
16
17
|
|
|
17
18
|
const descriptions = {
|
|
18
19
|
'start': 'A Start node with a required message parameter. Supply it through trigger.outputs.',
|
|
@@ -23,6 +24,7 @@ const descriptions = {
|
|
|
23
24
|
'poll': 'Gmail polling by key; replace CONNECTION_ID with an active Gmail connection.',
|
|
24
25
|
'integration': 'Telegram webhook integration by key; replace CONNECTION_ID with an active Telegram connection.',
|
|
25
26
|
'decision': 'AI Decision evaluates named questions against target and returns a complete answer object per output. Configure target before running.',
|
|
27
|
+
'dedupe': 'Filter items using persistent history. Records immediately, including when downstream work fails. Empty field uses each scalar item as identity.',
|
|
26
28
|
'http-request': 'A single HTTP request. Bind url and request inputs; outputs are body, statusCode and headers.',
|
|
27
29
|
'openapi': 'A fixed JSON API operation. Replace the example document and URL before running.',
|
|
28
30
|
'connector':
|
|
@@ -57,6 +59,12 @@ export function authoringExample(name: string): { version: 1; operations: readon
|
|
|
57
59
|
{ name: 'needs_support', type: 'noul', instructions: 'Does the customer need support?' },
|
|
58
60
|
])
|
|
59
61
|
break
|
|
62
|
+
case 'dedupe':
|
|
63
|
+
operations = [
|
|
64
|
+
...createManagedTask({ nodeId: 'dedupe' }, dedupeTask()),
|
|
65
|
+
{ kind: 'graph.node.input.set', nodeId: 'dedupe', handle: 'items', value: { kind: 'value', value: ['a', 'b', 'a'] } },
|
|
66
|
+
]
|
|
67
|
+
break
|
|
60
68
|
case 'http-request':
|
|
61
69
|
operations = [
|
|
62
70
|
...createManagedTask({ nodeId: 'http' }, httpRequestTask()),
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { retryConfigurationSchema, retryNodeTypes } from '../../flow/common/retry.ts'
|
|
13
13
|
import { httpRequestConfigSchema } from '../../http/common/httpRequest.ts'
|
|
14
14
|
import { currentEngineContract, findEngineContract } from '../../runtime/common/engineContract.ts'
|
|
15
|
+
import { dedupeConfigSchema } from '../../state/common/state.ts'
|
|
15
16
|
import { authoringLlmConfiguration } from './authoringLlm.ts'
|
|
16
17
|
import { authoringOpenApiSchema } from './authoringOpenApi.ts'
|
|
17
18
|
|
|
@@ -35,6 +36,7 @@ export const authoringTypes = [
|
|
|
35
36
|
'decision',
|
|
36
37
|
'openapi',
|
|
37
38
|
'http-request',
|
|
39
|
+
'dedupe',
|
|
38
40
|
'condition',
|
|
39
41
|
'value',
|
|
40
42
|
'wait',
|
|
@@ -118,6 +120,7 @@ export const authoringConfigurations = {
|
|
|
118
120
|
'connector': definitions.connector.omit({ outputMode: true }).strict(),
|
|
119
121
|
'openapi': authoringOpenApiSchema,
|
|
120
122
|
'http-request': httpRequestConfigSchema,
|
|
123
|
+
'dedupe': dedupeConfigSchema,
|
|
121
124
|
'agent': definitions.agent
|
|
122
125
|
.omit({ prompt: true, tools: true })
|
|
123
126
|
.extend({
|
|
@@ -283,7 +286,7 @@ export function authoringSchema(kind?: AuthoringType) {
|
|
|
283
286
|
}
|
|
284
287
|
: {
|
|
285
288
|
type: kind,
|
|
286
|
-
onError: retryNodeTypes.some((nodeType) => nodeType == kind) ? { enum: ['stop', 'error'] } : false,
|
|
289
|
+
onError: kind == 'dedupe' || retryNodeTypes.some((nodeType) => nodeType == kind) ? { enum: ['stop', 'error'] } : false,
|
|
287
290
|
retry: retryNodeTypes.some((nodeType) => nodeType == kind) ? z.toJSONSchema(retryConfigurationSchema) : false,
|
|
288
291
|
inputSources: ['poll', 'integration'].includes(kind)
|
|
289
292
|
? ['value', 'unset', 'default']
|
|
@@ -306,17 +309,19 @@ export function authoringSchema(kind?: AuthoringType) {
|
|
|
306
309
|
? 'Configure static parameters with node.add.inputs or input.set. Definitions come from the selected Trigger key; execution output and variable sources are unavailable.'
|
|
307
310
|
: kind == 'llm'
|
|
308
311
|
? 'Configure model settings, message templates and history as defaults. Input bindings with the same names override these defaults; named business inputs can be bound directly. Template strings interpolate {{name}}.'
|
|
309
|
-
: kind == '
|
|
310
|
-
? 'Configure
|
|
311
|
-
: kind == '
|
|
312
|
-
? '
|
|
313
|
-
: kind == '
|
|
314
|
-
? 'Select
|
|
315
|
-
: kind == '
|
|
316
|
-
? '
|
|
317
|
-
: kind == '
|
|
318
|
-
?
|
|
319
|
-
:
|
|
312
|
+
: kind == 'dedupe'
|
|
313
|
+
? 'Bind an items array. Configure field (dot-separated identity path, empty for scalar items), mode (new or increasing), and space (node or flow). Outputs are items and duplicates. History persists immediately; downstream failures do not roll it back. Increasing mode accepts finite numbers or RFC 3339 timestamps, never mixed types.'
|
|
314
|
+
: kind == 'http-request'
|
|
315
|
+
? 'Configure method, query/headers modes and names, bodyFormat, authentication, responseFormat and statusPolicy. Bind url, query.<name>/header.<name> or whole query/headers objects, body and auth.token (Basic: auth.username/auth.password). Credentials accept variables or upstream outputs only. Outputs: body, statusCode, headers. Authentication is replaced as a complete choice.'
|
|
316
|
+
: kind == 'openapi'
|
|
317
|
+
? 'Select an operation; the tool resolves its ports and authentication. Read authenticationChoices from node details. authentication is replaced as a complete choice. Bind advertised credentials through inputs.'
|
|
318
|
+
: kind == 'workflow'
|
|
319
|
+
? 'Select another workflow with config.flowId. A workflow cannot call itself, including in a disconnected node. Owned unpublished or unavailable targets may be saved as incomplete drafts. Use node.refresh to load the current published interface.'
|
|
320
|
+
: kind == 'agent'
|
|
321
|
+
? 'Tools accept Connector Actions or {kind: "workflow", flowId, name, description, approval, inputs}. Workflow parameter sources use model, input or value. The platform derives the interface. Refresh a workflow tool with node.refresh and tool: its name.'
|
|
322
|
+
: kind == 'code'
|
|
323
|
+
? codeGuidance
|
|
324
|
+
: 'Supply business configuration and input data. Fixed definitions are assembled by the tool. Existing constraints and defaults are preserved on partial updates.',
|
|
320
325
|
example: {
|
|
321
326
|
op: 'node.add',
|
|
322
327
|
as: 'step',
|
|
@@ -330,6 +335,7 @@ export function authoringSchema(kind?: AuthoringType) {
|
|
|
330
335
|
'connector': { config: { action: '<Action ID>', connectionId: '<Connection ID>' } },
|
|
331
336
|
'poll': { config: { key: '<Trigger key>', schedule: [{ type: 'every', unit: 'minute', value: 5 }] } },
|
|
332
337
|
'integration': { config: { key: '<Trigger key>' } },
|
|
338
|
+
'dedupe': { config: { field: 'id', mode: 'new', space: 'node' }, inputs: { items: { kind: 'value', value: [{ id: 'a' }, { id: 'b' }] } } },
|
|
333
339
|
'http-request': { config: {}, inputs: { url: { kind: 'value', value: 'https://example.com' } } },
|
|
334
340
|
'openapi': { config: { sourceUrl: 'https://example.com/openapi.json', path: '/items', method: 'get' } },
|
|
335
341
|
'code': { code: 'export default ({value}) => ({result:value})', inputs: { value: { kind: 'value', value: 'Hello' } } },
|
|
@@ -42,6 +42,9 @@ export const controlErrorCode = {
|
|
|
42
42
|
triggerKeyInvalid: 'trigger-key.invalid',
|
|
43
43
|
triggerKeyNotFound: 'trigger-key.not-found',
|
|
44
44
|
triggerNotFound: 'trigger.not-found',
|
|
45
|
+
tagInvalid: 'tag.invalid',
|
|
46
|
+
tagConflict: 'tag.conflict',
|
|
47
|
+
tagNotFound: 'tag.not-found',
|
|
45
48
|
variableInvalid: 'variable.invalid',
|
|
46
49
|
variableLimitReached: 'variable.limit-reached',
|
|
47
50
|
variableNotFound: 'variable.not-found',
|
|
@@ -93,6 +96,9 @@ export const controlErrorMetadata = {
|
|
|
93
96
|
[controlErrorCode.triggerKeyInvalid]: { status: 409 },
|
|
94
97
|
[controlErrorCode.triggerKeyNotFound]: { status: 404 },
|
|
95
98
|
[controlErrorCode.triggerNotFound]: { status: 404 },
|
|
99
|
+
[controlErrorCode.tagInvalid]: { status: 400 },
|
|
100
|
+
[controlErrorCode.tagConflict]: { status: 409 },
|
|
101
|
+
[controlErrorCode.tagNotFound]: { status: 404 },
|
|
96
102
|
[controlErrorCode.variableInvalid]: { status: 400 },
|
|
97
103
|
[controlErrorCode.variableLimitReached]: { status: 409 },
|
|
98
104
|
[controlErrorCode.variableNotFound]: { status: 404 },
|
|
@@ -60,6 +60,7 @@ export function flow(value: unknown): Flow {
|
|
|
60
60
|
revisionId: string(target.revisionId),
|
|
61
61
|
},
|
|
62
62
|
}),
|
|
63
|
+
tagIds: source.tagIds === undefined ? [] : strings(source.tagIds),
|
|
63
64
|
createdAt: string(source.createdAt),
|
|
64
65
|
...(source.connectorTeamId == null ? {} : { connectorTeamId: string(source.connectorTeamId) }),
|
|
65
66
|
draftRevisionId: string(source.draftRevisionId),
|
|
@@ -5,6 +5,7 @@ import { canvasCommandSchema } from '../../canvas/common/commands.ts'
|
|
|
5
5
|
import { runStatuses } from '../../execution/common/runLifecycle.ts'
|
|
6
6
|
import { waitCommentSchema } from '../../execution/common/wait.ts'
|
|
7
7
|
import { resourceNameIssue } from '../../flow/common/change.ts'
|
|
8
|
+
import { stateQuerySchema, stateClearSchema, dedupeClearSchema } from '../../state/common/state.ts'
|
|
8
9
|
import { authoringReadSchema, authoringSearchSchema, authoringRequestSchema, authoringSchemaQuery } from './authoringSchema.ts'
|
|
9
10
|
import { resultQuerySchema } from './resultQuery.ts'
|
|
10
11
|
|
|
@@ -50,6 +51,21 @@ function tool<Args>(description: string, schema: z.ZodType<Args>, readOnly: bool
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export const mcpTools = {
|
|
54
|
+
flow_state: tool(
|
|
55
|
+
'Inspect Flow state usage, business keys and deduplication spaces. Values are omitted unless requested. Scope is required.',
|
|
56
|
+
stateQuerySchema.extend({ flowId: flow }),
|
|
57
|
+
true,
|
|
58
|
+
),
|
|
59
|
+
flow_state_clear: tool(
|
|
60
|
+
'Delete one business state key or clear all business values. Explicit draft/live scope is required; live changes production state immediately.',
|
|
61
|
+
stateClearSchema.extend({ flowId: flow }),
|
|
62
|
+
false,
|
|
63
|
+
),
|
|
64
|
+
flow_dedupe_clear: tool(
|
|
65
|
+
'Clear one deduplication space (flow or node:<id>). Explicit draft/live scope is required; live changes production history immediately.',
|
|
66
|
+
dedupeClearSchema.extend({ flowId: flow }),
|
|
67
|
+
false,
|
|
68
|
+
),
|
|
53
69
|
flow_canvas: tool(
|
|
54
70
|
'Broadcast a transient canvas command to connected Workbenches viewing this Flow. Success only confirms Server broadcast, not delivery or execution. No replay, session selection or Presentation save. Pan uses screen pixels (positive right/down); zoom is an absolute ratio (0.1–3); fit includes all nodes. Repeating pan moves again.',
|
|
55
71
|
z.strictObject({ flowId: flow, command: canvasCommandSchema }),
|
|
@@ -18,6 +18,8 @@ const json: z.ZodType<JsonValue> = z.json()
|
|
|
18
18
|
const id = z.string().min(1)
|
|
19
19
|
const version = z.literal(1)
|
|
20
20
|
const flowName = id.refine((value) => value == value.trim() && resourceNameIssue(value) == null, 'Flow name is invalid.')
|
|
21
|
+
const tagIds = z.array(id).transform((ids) => [...new Set(ids)].toSorted())
|
|
22
|
+
const tagName = id.transform((name) => name.trim()).refine((name) => resourceNameIssue(name) == null, 'Tag name is invalid.')
|
|
21
23
|
const inputs = z.record(z.string(), z.record(z.string(), json))
|
|
22
24
|
const trigger = z.strictObject({ nodeId: id, outputs: z.record(z.string(), json) })
|
|
23
25
|
const schemas = {
|
|
@@ -31,8 +33,11 @@ const schemas = {
|
|
|
31
33
|
createEventSource: createEventSourceSchema,
|
|
32
34
|
updateEventSource: updateEventSourceSchema,
|
|
33
35
|
eventSourceRevision: eventSourceRevisionSchema,
|
|
34
|
-
importFlow: z.strictObject({ file: json, name: flowName.optional(), teamId: id.optional(), version }),
|
|
35
|
-
createFlow: z.strictObject({ name: flowName, teamId: id.optional(), version }),
|
|
36
|
+
importFlow: z.strictObject({ file: json, name: flowName.optional(), teamId: id.optional(), tagIds: tagIds.optional(), version }),
|
|
37
|
+
createFlow: z.strictObject({ name: flowName, teamId: id.optional(), tagIds: tagIds.optional(), version }),
|
|
38
|
+
saveTag: z.strictObject({ name: tagName, version }),
|
|
39
|
+
setFlowTags: z.strictObject({ tagIds, version }),
|
|
40
|
+
updateFlow: z.union([z.strictObject({ name: flowName, tagIds: tagIds.optional(), version }), z.strictObject({ name: flowName.optional(), tagIds, version })]),
|
|
36
41
|
renameFlow: z.strictObject({ name: flowName, version }),
|
|
37
42
|
changeDraft: z.strictObject({ expectedRevisionId: id, operations: z.array(json).min(1), version }),
|
|
38
43
|
repairDraft: z.strictObject({ expectedRevisionId: id, version }),
|
|
@@ -79,6 +84,9 @@ export const controlRequests = {
|
|
|
79
84
|
eventSourceRevision: decoder(schemas.eventSourceRevision),
|
|
80
85
|
importFlow: decoder(schemas.importFlow),
|
|
81
86
|
createFlow: decoder(schemas.createFlow),
|
|
87
|
+
saveTag: decoder(schemas.saveTag),
|
|
88
|
+
setFlowTags: decoder(schemas.setFlowTags),
|
|
89
|
+
updateFlow: decoder(schemas.updateFlow),
|
|
82
90
|
renameFlow: decoder(schemas.renameFlow),
|
|
83
91
|
changeDraft(value: unknown): { expectedRevisionId: string; operations: readonly DraftOperation[]; version: 1 } {
|
|
84
92
|
const body = schemas.changeDraft.parse(value)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FlowQuery, Tag, TagListItem } from './api.ts'
|
|
2
|
+
|
|
3
|
+
import { integer, invalidResponse, record, string } from './decoding.ts'
|
|
4
|
+
|
|
5
|
+
export function normalizeFlowQuery(query: FlowQuery = {}): Required<FlowQuery> {
|
|
6
|
+
return { query: query.query?.trim().toLowerCase() ?? '', tagIds: [...new Set(query.tagIds ?? [])].toSorted(), untagged: query.untagged ?? false }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function decodeTag(value: unknown): Tag {
|
|
10
|
+
const source = record(value)
|
|
11
|
+
if (source.version != 1) return invalidResponse()
|
|
12
|
+
return { tagId: string(source.tagId), name: string(source.name), version: 1 }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function decodeTagListItem(value: unknown): TagListItem {
|
|
16
|
+
const source = record(value)
|
|
17
|
+
const referenceCount = integer(source.referenceCount)
|
|
18
|
+
if (referenceCount < 0) return invalidResponse()
|
|
19
|
+
return { ...decodeTag(source), referenceCount }
|
|
20
|
+
}
|
|
@@ -23,6 +23,7 @@ const nodeKinds = new Set([
|
|
|
23
23
|
'javascript',
|
|
24
24
|
'openapi',
|
|
25
25
|
'http-request',
|
|
26
|
+
'dedupe',
|
|
26
27
|
'llm',
|
|
27
28
|
'value',
|
|
28
29
|
'wait',
|
|
@@ -56,6 +57,7 @@ function nodeKind(
|
|
|
56
57
|
| 'javascript'
|
|
57
58
|
| 'openapi'
|
|
58
59
|
| 'http-request'
|
|
60
|
+
| 'dedupe'
|
|
59
61
|
| 'llm'
|
|
60
62
|
| 'value'
|
|
61
63
|
| 'wait'
|
|
@@ -76,6 +78,7 @@ function nodeKind(
|
|
|
76
78
|
| 'javascript'
|
|
77
79
|
| 'openapi'
|
|
78
80
|
| 'http-request'
|
|
81
|
+
| 'dedupe'
|
|
79
82
|
| 'llm'
|
|
80
83
|
| 'value'
|
|
81
84
|
| 'wait'
|
|
@@ -109,6 +109,7 @@ function nodeKind(
|
|
|
109
109
|
| 'javascript'
|
|
110
110
|
| 'openapi'
|
|
111
111
|
| 'http-request'
|
|
112
|
+
| 'dedupe'
|
|
112
113
|
| 'llm'
|
|
113
114
|
| 'value'
|
|
114
115
|
| 'wait'
|
|
@@ -301,6 +302,7 @@ export function executeNode(
|
|
|
301
302
|
readonly node: ExecutableNode
|
|
302
303
|
readonly jobId: string
|
|
303
304
|
readonly nodeInputs: Readonly<Record<string, JsonValue>>
|
|
305
|
+
readonly launchInputs?: Readonly<Record<string, JsonValue>>
|
|
304
306
|
readonly flowId: string
|
|
305
307
|
readonly runId: string
|
|
306
308
|
readonly graph: Graph
|
|
@@ -368,6 +370,7 @@ export function executeNode(
|
|
|
368
370
|
if (additional.has(handle)) return false
|
|
369
371
|
const task = 'executor' in node.task ? node.task : undefined
|
|
370
372
|
if (task == null || !['openapi', 'http-request'].includes(task.executor.kind)) return true
|
|
373
|
+
if (Object.hasOwn(input.launchInputs ?? {}, handle)) return true
|
|
371
374
|
const mapping = node.inputs[handle]
|
|
372
375
|
return (
|
|
373
376
|
mapping?.kind != 'unset' &&
|
|
@@ -70,6 +70,7 @@ export type SchedulerEvent =
|
|
|
70
70
|
| 'javascript'
|
|
71
71
|
| 'openapi'
|
|
72
72
|
| 'http-request'
|
|
73
|
+
| 'dedupe'
|
|
73
74
|
| 'llm'
|
|
74
75
|
| 'value'
|
|
75
76
|
| 'wait'
|
|
@@ -344,6 +345,7 @@ function runGraph(
|
|
|
344
345
|
node,
|
|
345
346
|
jobId,
|
|
346
347
|
nodeInputs,
|
|
348
|
+
launchInputs: launch[nodeId],
|
|
347
349
|
resolution,
|
|
348
350
|
flowId: target.flowId,
|
|
349
351
|
runId,
|