flowent 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +3 -3
  2. package/backend/README.md +3 -3
  3. package/backend/pyproject.toml +1 -1
  4. package/backend/src/flowent/agent.py +1 -1
  5. package/backend/src/flowent/api_models.py +103 -0
  6. package/backend/src/flowent/app.py +151 -0
  7. package/backend/src/flowent/cli.py +13 -4
  8. package/backend/src/flowent/compact.py +34 -13
  9. package/backend/src/flowent/llm.py +2 -0
  10. package/backend/src/flowent/main.py +18 -1994
  11. package/backend/src/flowent/mcp.py +100 -2
  12. package/backend/src/flowent/network.py +5 -0
  13. package/backend/src/flowent/provider_connections.py +42 -0
  14. package/backend/src/flowent/routes/__init__.py +0 -0
  15. package/backend/src/flowent/routes/integrations.py +105 -0
  16. package/backend/src/flowent/routes/permissions.py +36 -0
  17. package/backend/src/flowent/routes/providers.py +30 -0
  18. package/backend/src/flowent/routes/system.py +49 -0
  19. package/backend/src/flowent/routes/workflow_routes.py +63 -0
  20. package/backend/src/flowent/routes/workspace.py +105 -0
  21. package/backend/src/flowent/state/__init__.py +53 -0
  22. package/backend/src/flowent/state/models.py +257 -0
  23. package/backend/src/flowent/state/schema.py +186 -0
  24. package/backend/src/flowent/state/store.py +1013 -0
  25. package/backend/src/flowent/static/assets/index-CvWZZMtK.css +2 -0
  26. package/backend/src/flowent/static/assets/index-ma2v8oW7.js +90 -0
  27. package/backend/src/flowent/static/index.html +2 -2
  28. package/backend/src/flowent/storage.py +52 -1318
  29. package/backend/src/flowent/system_tools.py +25 -0
  30. package/backend/src/flowent/tools.py +4 -2
  31. package/backend/src/flowent/usage.py +9 -4
  32. package/backend/src/flowent/workflows.py +282 -0
  33. package/backend/src/flowent/workspace/__init__.py +0 -0
  34. package/backend/src/flowent/workspace/context.py +249 -0
  35. package/backend/src/flowent/workspace/events.py +180 -0
  36. package/backend/src/flowent/workspace/output.py +274 -0
  37. package/backend/src/flowent/workspace/runtime.py +1041 -0
  38. package/backend/uv.lock +1 -1
  39. package/dist/frontend/assets/index-CvWZZMtK.css +2 -0
  40. package/dist/frontend/assets/index-ma2v8oW7.js +90 -0
  41. package/dist/frontend/index.html +2 -2
  42. package/package.json +1 -1
  43. package/backend/src/flowent/static/assets/index-BH30iLzb.css +0 -2
  44. package/backend/src/flowent/static/assets/index-sBFt3ORj.js +0 -84
  45. package/dist/frontend/assets/index-BH30iLzb.css +0 -2
  46. package/dist/frontend/assets/index-sBFt3ORj.js +0 -84
@@ -0,0 +1,257 @@
1
+ from typing import Annotated, Literal
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field, PositiveInt
4
+
5
+ from flowent.llm import ChatMessage, ProviderFormat, ReasoningEffort
6
+ from flowent.usage import TokenUsageInfo
7
+
8
+
9
+ class StoredTelegramSession(BaseModel):
10
+ model_config = ConfigDict(extra="forbid")
11
+
12
+ chat_id: str
13
+ display_name: str = ""
14
+ recent_message: str = ""
15
+ status: str
16
+ updated_at: int = 0
17
+ user_id: str = ""
18
+ username: str = ""
19
+
20
+
21
+ class StoredTelegramBot(BaseModel):
22
+ model_config = ConfigDict(extra="forbid")
23
+
24
+ bot_token: str
25
+ enabled: bool
26
+ error: str = ""
27
+ sessions: list[StoredTelegramSession] = Field(default_factory=list)
28
+ status: str = "disabled"
29
+
30
+
31
+ class StoredMcpTool(BaseModel):
32
+ model_config = ConfigDict(extra="forbid")
33
+
34
+ description: str = ""
35
+ input_schema: dict[str, object] = Field(default_factory=dict)
36
+ name: str
37
+ output_schema: dict[str, object] | None = None
38
+
39
+
40
+ class StoredMcpServer(BaseModel):
41
+ model_config = ConfigDict(extra="forbid")
42
+
43
+ args: list[str] = Field(default_factory=list)
44
+ command: str = ""
45
+ config: dict[str, object] = Field(default_factory=dict)
46
+ enabled: bool = True
47
+ error: str = ""
48
+ id: str
49
+ name: str
50
+ status: str = "disabled"
51
+ tools: list[StoredMcpTool] = Field(default_factory=list)
52
+ type: str
53
+ url: str = ""
54
+
55
+
56
+ class StoredSkill(BaseModel):
57
+ model_config = ConfigDict(extra="forbid")
58
+
59
+ description: str
60
+ enabled: bool = True
61
+ error: str = ""
62
+ id: str
63
+ name: str
64
+ path: str
65
+ scope: str
66
+ slug: str
67
+
68
+
69
+ class StoredWritablePath(BaseModel):
70
+ model_config = ConfigDict(extra="forbid")
71
+
72
+ created_at: int = 0
73
+ path: str
74
+
75
+
76
+ class StoredWorkflowNodePosition(BaseModel):
77
+ model_config = ConfigDict(extra="forbid")
78
+
79
+ x: float = 0
80
+ y: float = 0
81
+
82
+
83
+ class StoredWorkflowNode(BaseModel):
84
+ model_config = ConfigDict(extra="forbid")
85
+
86
+ data: dict[str, object] = Field(default_factory=dict)
87
+ description: str = ""
88
+ id: str
89
+ name: str
90
+ position: StoredWorkflowNodePosition = Field(
91
+ default_factory=StoredWorkflowNodePosition
92
+ )
93
+ type: Literal["input", "agent", "merge", "output"]
94
+
95
+
96
+ class StoredWorkflowEdge(BaseModel):
97
+ model_config = ConfigDict(extra="forbid")
98
+
99
+ id: str
100
+ label: str = ""
101
+ source: str
102
+ source_handle: str = ""
103
+ target: str
104
+ target_handle: str = ""
105
+
106
+
107
+ class StoredWorkflowDefinition(BaseModel):
108
+ model_config = ConfigDict(extra="forbid")
109
+
110
+ edges: list[StoredWorkflowEdge] = Field(default_factory=list)
111
+ nodes: list[StoredWorkflowNode] = Field(default_factory=list)
112
+ version: int = 1
113
+
114
+
115
+ class StoredWorkflow(BaseModel):
116
+ model_config = ConfigDict(extra="forbid")
117
+
118
+ created_at: int = 0
119
+ definition: StoredWorkflowDefinition
120
+ id: str
121
+ name: str
122
+ updated_at: int = 0
123
+
124
+
125
+ class StoredProvider(BaseModel):
126
+ model_config = ConfigDict(extra="forbid")
127
+
128
+ api_key: str
129
+ base_url: str
130
+ id: str
131
+ models: list[str]
132
+ name: str
133
+ type: ProviderFormat
134
+
135
+
136
+ class StoredSettings(BaseModel):
137
+ model_config = ConfigDict(extra="forbid")
138
+
139
+ agent_prompt: str = Field(default="", exclude_if=lambda value: value == "")
140
+ context_window_limit: PositiveInt | None = None
141
+ reasoning_effort: ReasoningEffort = ReasoningEffort.DEFAULT
142
+ selected_model: str
143
+ selected_provider_id: str
144
+
145
+
146
+ class StoredToolItem(BaseModel):
147
+ model_config = ConfigDict(extra="forbid")
148
+
149
+ id: str
150
+ name: str
151
+ status: str
152
+ title: str
153
+ arguments: dict[str, object] | None = None
154
+ content: str | None = None
155
+ data: dict[str, object] | None = None
156
+
157
+
158
+ class StoredThinkingOutputItem(BaseModel):
159
+ model_config = ConfigDict(extra="forbid")
160
+
161
+ content: str
162
+ id: str
163
+ type: Literal["thinking"]
164
+
165
+
166
+ class StoredTextOutputItem(BaseModel):
167
+ model_config = ConfigDict(extra="forbid")
168
+
169
+ content: str
170
+ id: str
171
+ type: Literal["text"]
172
+
173
+
174
+ class StoredErrorOutputItem(BaseModel):
175
+ model_config = ConfigDict(extra="forbid")
176
+
177
+ detail: str = Field(default="", exclude_if=lambda value: value == "")
178
+ id: str
179
+ message: str
180
+ title: str
181
+ type: Literal["error"]
182
+
183
+
184
+ class StoredToolOutputItem(BaseModel):
185
+ model_config = ConfigDict(extra="forbid")
186
+
187
+ id: str
188
+ tool: StoredToolItem
189
+ type: Literal["tool"]
190
+
191
+
192
+ StoredOutputItem = Annotated[
193
+ StoredThinkingOutputItem
194
+ | StoredTextOutputItem
195
+ | StoredErrorOutputItem
196
+ | StoredToolOutputItem,
197
+ Field(discriminator="type"),
198
+ ]
199
+
200
+
201
+ class StoredAssistantOutputGroup(BaseModel):
202
+ model_config = ConfigDict(extra="forbid")
203
+
204
+ id: str
205
+ items: list[StoredOutputItem]
206
+
207
+
208
+ class StoredMessage(BaseModel):
209
+ model_config = ConfigDict(extra="forbid")
210
+
211
+ author: str
212
+ content: str
213
+ groups: list[StoredAssistantOutputGroup] = Field(
214
+ default_factory=list, exclude_if=lambda value: value == []
215
+ )
216
+ id: str
217
+ status: str = Field(
218
+ default="completed", exclude_if=lambda value: value == "completed"
219
+ )
220
+ thinking: str = Field(default="", exclude_if=lambda value: value == "")
221
+ tools: list[StoredToolItem] = Field(default_factory=list)
222
+ usage_info: TokenUsageInfo | None = Field(
223
+ default=None, exclude_if=lambda value: value is None
224
+ )
225
+
226
+
227
+ class StoredCompactionCheckpoint(BaseModel):
228
+ model_config = ConfigDict(extra="forbid")
229
+
230
+ created_at: int = 0
231
+ id: str
232
+ method: str
233
+ replacement_history: list[ChatMessage]
234
+ source_message_id: str | None = None
235
+ summary: str
236
+ token_after: int = 0
237
+ token_before: int = 0
238
+ trigger: str
239
+
240
+
241
+ class StoredState(BaseModel):
242
+ model_config = ConfigDict(extra="forbid")
243
+
244
+ active_run_event_index: int = 0
245
+ active_run_id: str | None = None
246
+ is_compacting: bool = False
247
+ mcp_servers: list[StoredMcpServer]
248
+ messages: list[StoredMessage]
249
+ providers: list[StoredProvider]
250
+ settings: StoredSettings
251
+ skills: list[StoredSkill]
252
+ telegram_bot: StoredTelegramBot
253
+ usage_info: TokenUsageInfo | None = Field(
254
+ default=None, exclude_if=lambda value: value is None
255
+ )
256
+ writable_paths: list[StoredWritablePath] = Field(default_factory=list)
257
+ workflows: list[StoredWorkflow] = Field(default_factory=list)
@@ -0,0 +1,186 @@
1
+ import sqlite3
2
+
3
+
4
+ def migrate(connection: sqlite3.Connection) -> None:
5
+ connection.executescript(
6
+ """
7
+ CREATE TABLE IF NOT EXISTS workflows (
8
+ id TEXT PRIMARY KEY,
9
+ name TEXT NOT NULL,
10
+ definition TEXT NOT NULL,
11
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
12
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
13
+ );
14
+
15
+ CREATE TABLE IF NOT EXISTS mcp_servers (
16
+ id TEXT PRIMARY KEY,
17
+ name TEXT NOT NULL,
18
+ type TEXT NOT NULL,
19
+ command TEXT NOT NULL DEFAULT '',
20
+ args TEXT NOT NULL DEFAULT '[]',
21
+ config TEXT NOT NULL DEFAULT '{}',
22
+ url TEXT NOT NULL DEFAULT '',
23
+ enabled INTEGER NOT NULL DEFAULT 1,
24
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
25
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS mcp_tools (
29
+ server_id TEXT NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
30
+ name TEXT NOT NULL,
31
+ description TEXT NOT NULL DEFAULT '',
32
+ input_schema TEXT NOT NULL DEFAULT '{}',
33
+ output_schema TEXT,
34
+ position INTEGER NOT NULL,
35
+ PRIMARY KEY (server_id, name)
36
+ );
37
+
38
+ CREATE TABLE IF NOT EXISTS telegram_bot (
39
+ id INTEGER PRIMARY KEY CHECK (id = 1),
40
+ enabled INTEGER NOT NULL DEFAULT 0,
41
+ bot_token TEXT NOT NULL,
42
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
43
+ );
44
+
45
+ CREATE TABLE IF NOT EXISTS telegram_sessions (
46
+ chat_id TEXT PRIMARY KEY,
47
+ user_id TEXT NOT NULL DEFAULT '',
48
+ username TEXT NOT NULL DEFAULT '',
49
+ display_name TEXT NOT NULL DEFAULT '',
50
+ recent_message TEXT NOT NULL DEFAULT '',
51
+ status TEXT NOT NULL,
52
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
53
+ );
54
+
55
+ CREATE TABLE IF NOT EXISTS providers (
56
+ id TEXT PRIMARY KEY,
57
+ name TEXT NOT NULL,
58
+ type TEXT NOT NULL,
59
+ base_url TEXT NOT NULL,
60
+ api_key TEXT NOT NULL,
61
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
62
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
63
+ );
64
+
65
+ CREATE TABLE IF NOT EXISTS provider_models (
66
+ provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
67
+ model TEXT NOT NULL,
68
+ position INTEGER NOT NULL,
69
+ PRIMARY KEY (provider_id, model)
70
+ );
71
+
72
+ CREATE TABLE IF NOT EXISTS settings (
73
+ id INTEGER PRIMARY KEY CHECK (id = 1),
74
+ selected_provider_id TEXT NOT NULL DEFAULT '',
75
+ selected_model TEXT NOT NULL DEFAULT '',
76
+ reasoning_effort TEXT NOT NULL DEFAULT 'default',
77
+ agent_prompt TEXT NOT NULL DEFAULT '',
78
+ context_window_limit INTEGER,
79
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
80
+ );
81
+
82
+ CREATE TABLE IF NOT EXISTS messages (
83
+ id TEXT PRIMARY KEY,
84
+ author TEXT NOT NULL,
85
+ content TEXT NOT NULL,
86
+ status TEXT NOT NULL DEFAULT 'completed',
87
+ usage_info TEXT,
88
+ position INTEGER NOT NULL
89
+ );
90
+
91
+ CREATE TABLE IF NOT EXISTS workspace_context (
92
+ id INTEGER PRIMARY KEY CHECK (id = 1),
93
+ compacted_summary TEXT NOT NULL DEFAULT '',
94
+ active_compaction_id TEXT,
95
+ is_compacting INTEGER NOT NULL DEFAULT 0,
96
+ usage_info TEXT,
97
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
98
+ );
99
+
100
+ CREATE TABLE IF NOT EXISTS compaction_checkpoints (
101
+ id TEXT PRIMARY KEY,
102
+ trigger TEXT NOT NULL,
103
+ method TEXT NOT NULL,
104
+ summary TEXT NOT NULL,
105
+ replacement_history TEXT NOT NULL DEFAULT '[]',
106
+ source_message_id TEXT,
107
+ token_before INTEGER NOT NULL DEFAULT 0,
108
+ token_after INTEGER NOT NULL DEFAULT 0,
109
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
110
+ );
111
+
112
+ CREATE TABLE IF NOT EXISTS skill_settings (
113
+ id TEXT PRIMARY KEY,
114
+ enabled INTEGER NOT NULL DEFAULT 1,
115
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
116
+ );
117
+
118
+ CREATE TABLE IF NOT EXISTS writable_paths (
119
+ path TEXT PRIMARY KEY,
120
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
121
+ );
122
+
123
+ CREATE TABLE IF NOT EXISTS schema_migrations (
124
+ version INTEGER PRIMARY KEY
125
+ );
126
+
127
+ INSERT OR IGNORE INTO schema_migrations (version) VALUES (1);
128
+ """
129
+ )
130
+ mcp_server_columns = table_columns(connection, "mcp_servers")
131
+ if "config" not in mcp_server_columns:
132
+ connection.execute(
133
+ """
134
+ ALTER TABLE mcp_servers
135
+ ADD COLUMN config TEXT NOT NULL DEFAULT '{}'
136
+ """
137
+ )
138
+ message_columns = table_columns(connection, "messages")
139
+ if "tools" not in message_columns:
140
+ connection.execute(
141
+ "ALTER TABLE messages ADD COLUMN tools TEXT NOT NULL DEFAULT '[]'"
142
+ )
143
+ if "thinking" not in message_columns:
144
+ connection.execute(
145
+ "ALTER TABLE messages ADD COLUMN thinking TEXT NOT NULL DEFAULT ''"
146
+ )
147
+ if "status" not in message_columns:
148
+ connection.execute(
149
+ "ALTER TABLE messages ADD COLUMN status TEXT NOT NULL DEFAULT 'completed'"
150
+ )
151
+ if "groups" not in message_columns:
152
+ connection.execute(
153
+ "ALTER TABLE messages ADD COLUMN groups TEXT NOT NULL DEFAULT '[]'"
154
+ )
155
+ if "usage_info" not in message_columns:
156
+ connection.execute("ALTER TABLE messages ADD COLUMN usage_info TEXT")
157
+ settings_columns = table_columns(connection, "settings")
158
+ if "reasoning_effort" not in settings_columns:
159
+ connection.execute(
160
+ "ALTER TABLE settings "
161
+ "ADD COLUMN reasoning_effort TEXT NOT NULL DEFAULT 'default'"
162
+ )
163
+ if "agent_prompt" not in settings_columns:
164
+ connection.execute(
165
+ "ALTER TABLE settings ADD COLUMN agent_prompt TEXT NOT NULL DEFAULT ''"
166
+ )
167
+ if "context_window_limit" not in settings_columns:
168
+ connection.execute(
169
+ "ALTER TABLE settings ADD COLUMN context_window_limit INTEGER"
170
+ )
171
+ workspace_context_columns = table_columns(connection, "workspace_context")
172
+ if "active_compaction_id" not in workspace_context_columns:
173
+ connection.execute(
174
+ "ALTER TABLE workspace_context ADD COLUMN active_compaction_id TEXT"
175
+ )
176
+ if "usage_info" not in workspace_context_columns:
177
+ connection.execute("ALTER TABLE workspace_context ADD COLUMN usage_info TEXT")
178
+ if "is_compacting" not in workspace_context_columns:
179
+ connection.execute(
180
+ "ALTER TABLE workspace_context "
181
+ "ADD COLUMN is_compacting INTEGER NOT NULL DEFAULT 0"
182
+ )
183
+
184
+
185
+ def table_columns(connection: sqlite3.Connection, table: str) -> set[str]:
186
+ return {row["name"] for row in connection.execute(f"PRAGMA table_info({table})")}