neoagent 2.4.2-beta.5 → 2.4.2-beta.7

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 (44) hide show
  1. package/README.md +48 -18
  2. package/flutter_app/lib/main_controller.dart +0 -1
  3. package/flutter_app/lib/main_models.dart +28 -0
  4. package/flutter_app/lib/main_operations.dart +1 -1
  5. package/flutter_app/lib/main_settings.dart +9 -14
  6. package/flutter_app/lib/main_shared.dart +0 -9
  7. package/flutter_app/lib/main_unified.dart +0 -2
  8. package/flutter_app/lib/src/backend_client.dart +8 -0
  9. package/package.json +1 -1
  10. package/server/db/database.js +112 -0
  11. package/server/http/routes.js +1 -0
  12. package/server/public/.last_build_id +1 -1
  13. package/server/public/flutter_bootstrap.js +2 -2
  14. package/server/public/main.dart.js +10083 -10063
  15. package/server/routes/mcp.js +9 -0
  16. package/server/routes/memory.js +35 -2
  17. package/server/routes/runtime.js +7 -1
  18. package/server/routes/settings.js +34 -1
  19. package/server/routes/skills.js +42 -0
  20. package/server/routes/task_webhooks.js +65 -0
  21. package/server/services/ai/engine.js +525 -24
  22. package/server/services/ai/learning.js +56 -8
  23. package/server/services/ai/providers/anthropic.js +40 -8
  24. package/server/services/ai/providers/google.js +10 -0
  25. package/server/services/ai/providers/openai.js +3 -15
  26. package/server/services/ai/providers/openaiCompatible.js +11 -0
  27. package/server/services/ai/repetitionGuard.js +60 -0
  28. package/server/services/ai/systemPrompt.js +52 -16
  29. package/server/services/ai/taskAnalysis.js +15 -6
  30. package/server/services/ai/toolEvidence.js +3 -1
  31. package/server/services/ai/toolRunner.js +43 -1
  32. package/server/services/ai/toolSelector.js +92 -46
  33. package/server/services/ai/tools.js +89 -9
  34. package/server/services/ai/usage.js +114 -0
  35. package/server/services/manager.js +34 -0
  36. package/server/services/memory/manager.js +170 -4
  37. package/server/services/security/capability_audit.js +107 -0
  38. package/server/services/tasks/adapters/index.js +1 -0
  39. package/server/services/tasks/adapters/webhook.js +14 -0
  40. package/server/services/tasks/runtime.js +1 -1
  41. package/server/services/tasks/webhooks.js +146 -0
  42. package/server/services/workspace/code_navigation.js +194 -0
  43. package/server/services/workspace/structured_data.js +93 -0
  44. package/server/utils/cloud-security.js +24 -2
package/README.md CHANGED
@@ -6,53 +6,83 @@
6
6
 
7
7
  <p align="center"><strong>Your agent. Your server. Your rules.</strong></p>
8
8
 
9
+ <p align="center">A self-hosted AI agent that runs as a service, operates Android over ADB, and connects to 15+ messaging platforms while keeping credentials on your server.</p>
10
+
9
11
  <p align="center">
12
+ <a href="https://www.npmjs.com/package/neoagent"><img src="https://img.shields.io/npm/v/neoagent?style=flat-square&label=npm" alt="npm version"></a>
10
13
  <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node.js-20+-5fa04e?style=flat-square&logo=node.js&logoColor=white" alt="Node.js"></a>
11
14
  <a href="https://sqlite.org"><img src="https://img.shields.io/badge/SQLite-WAL-003b57?style=flat-square&logo=sqlite&logoColor=white" alt="SQLite"></a>
12
15
  <a href="https://flutter.dev"><img src="https://img.shields.io/badge/Flutter-web%20%2B%20android-02569B?style=flat-square&logo=flutter&logoColor=white" alt="Flutter"></a>
13
16
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-AGPL--3.0-a855f7?style=flat-square" alt="License"></a>
14
17
  </p>
15
18
 
16
- <p align="center">Self-hosted AI agent — runs as a system service, controls Android over ADB, connects to 15+ messaging platforms, all credentials on your server.</p>
17
-
18
19
  <p align="center">
19
20
  <img src="demo.gif" alt="NeoAgent demo" width="100%">
20
21
  </p>
21
22
 
22
- | | | | |
23
- | --- | --- | --- | --- |
24
- | <img alt="WebUI" src="https://github.com/user-attachments/assets/3c76d59a-b6e3-4698-929b-9c94741ccf1e" height="420"> | <img height="494" alt="Android" src="https://github.com/user-attachments/assets/e8a0af7a-6881-485d-ad52-f3bc6f2023ca"> | <img alt="Mobile Telegram" src="https://github.com/user-attachments/assets/1fd41a9b-5452-4aa4-9478-888c8ad7363a" height="420"> | <img height="494" alt="image" src="https://github.com/user-attachments/assets/d5a57282-0851-4902-9588-d8de4b82d45c"> |
23
+ ## Why NeoAgent
25
24
 
26
- - **Android control** — screenshot, observe UI, tap, swipe, type, launch apps, install APKs, `adb shell` — the agent operates Android, not just an app running on it
27
- - **15+ messaging platforms** — Telegram, WhatsApp, Discord, Signal, Slack, Matrix, iMessage, IRC, LINE, Mattermost, Telnyx Voice
28
- - **Integrations** Google Workspace, Microsoft 365, Notion, Home Assistant, Trello, Spotify, Figma
29
- - **Browser + shell** — VM-isolated server-side browser automation, full PTY terminal
30
- - **Runs locally** — Ollama support, no API key required; credentials stay in `~/.neoagent/.env`, never in the client
25
+ | Android that the agent can operate | Credentials that stay server-side |
26
+ | --- | --- |
27
+ | NeoAgent can inspect the UI, take screenshots, tap, swipe, type, launch apps, install APKs, and run `adb shell` against a connected device or emulator. | API keys, OAuth tokens, messaging credentials, history, and runtime data are stored under `~/.neoagent` on the server, never in the client. |
31
28
 
32
- ## Install
29
+ ## Quick Start
30
+
31
+ Requires Node.js 20 or newer.
33
32
 
34
33
  ```bash
35
34
  npm install -g neoagent
36
35
  neoagent install
37
36
  ```
38
37
 
39
- `neoagent install` runs a preflight, writes a secure default config when setup
40
- cannot prompt, installs Node dependencies, builds or uses the bundled web
41
- client, bootstraps supported system tools where possible, and starts the
42
- service. Available at **http://localhost:3333** when complete.
38
+ `neoagent install` checks the host, creates secure runtime configuration,
39
+ installs dependencies and supported system tools, and starts the service. Open
40
+ **http://localhost:3333** when it finishes.
41
+
42
+ No hosted-model key is required when using local [Ollama](https://ollama.com/).
43
+ See [Getting Started](docs/getting-started.md) for prerequisites and first-run
44
+ setup.
45
+
46
+ ## What It Does
47
+
48
+ - **15+ messaging platforms**: Telegram, WhatsApp, Discord, Signal, Slack, Matrix, iMessage, Teams, IRC, LINE, Mattermost, Telnyx Voice, and webhook bridges.
49
+ - **Automation**: cron schedules, integration and weather triggers, reusable skills, MCP tools, and subagents.
50
+ - **Browser and shell**: an isolated browser runtime plus a full PTY terminal on the NeoAgent server.
51
+ - **Integrations**: Google Workspace, Microsoft 365, Notion, Home Assistant, Trello, Spotify, Slack, Figma, GitHub, and more.
52
+ - **Recordings and memory**: audio capture, transcription, transcript search, long-term memory, session history, and health summaries.
53
+ - **Model choice**: Anthropic, OpenAI, Gemini, Grok, MiniMax, NVIDIA NIM, OpenRouter, GitHub Copilot, OpenAI Codex, or local Ollama.
43
54
 
44
- ## Manage
55
+ ## Interfaces
56
+
57
+ | | | | |
58
+ | --- | --- | --- | --- |
59
+ | <img alt="WebUI" src="https://github.com/user-attachments/assets/3c76d59a-b6e3-4698-929b-9c94741ccf1e" height="420"> | <img height="494" alt="Android" src="https://github.com/user-attachments/assets/e8a0af7a-6881-485d-ad52-f3bc6f2023ca"> | <img alt="Mobile Telegram" src="https://github.com/user-attachments/assets/1fd41a9b-5452-4aa4-9478-888c8ad7363a" height="420"> | <img height="494" alt="image" src="https://github.com/user-attachments/assets/d5a57282-0851-4902-9588-d8de4b82d45c"> |
60
+
61
+ ## Service Commands
45
62
 
46
63
  ```bash
47
64
  neoagent status
65
+ neoagent start
66
+ neoagent stop
67
+ neoagent restart
48
68
  neoagent update
49
69
  neoagent fix
50
70
  neoagent logs
51
71
  ```
52
72
 
53
- ## Links
73
+ ## Project Status
74
+
75
+ NeoAgent is beta software maintained by one person. Expect rough edges, and
76
+ please report failures with enough detail to reproduce them. Contributions to
77
+ the backend, Flutter clients, integrations, skills, tests, and documentation
78
+ are welcome.
79
+
80
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. Security
81
+ issues should be reported privately according to [SECURITY.md](SECURITY.md).
82
+
83
+ ## Documentation
54
84
 
55
- [Docs](https://neolabs-systems.github.io/NeoAgent/docs/) | [Getting Started](docs/getting-started.md) | [Configuration](docs/configuration.md) | [Capabilities](docs/capabilities.md) | [Why NeoAgent](docs/why-neoagent.md) | [Issues](https://github.com/NeoLabs-Systems/NeoAgent/issues)
85
+ [Docs](https://neolabs-systems.github.io/NeoAgent/docs/) | [Getting Started](docs/getting-started.md) | [Configuration](docs/configuration.md) | [Capabilities](docs/capabilities.md) | [Skills and MCP](docs/skills.md) | [Operations](docs/operations.md) | [Discussions](https://github.com/NeoLabs-Systems/NeoAgent/discussions) | [Issues](https://github.com/NeoLabs-Systems/NeoAgent/issues)
56
86
 
57
87
  ---
58
88
 
@@ -1658,7 +1658,6 @@ class NeoAgentController extends ChangeNotifier {
1658
1658
  }
1659
1659
 
1660
1660
  void setSelectedSection(AppSection section) {
1661
- final previousSection = selectedSection;
1662
1661
  selectedSection = section;
1663
1662
  unawaited(_prefs?.setString(_selectedSectionPrefsKey, section.name));
1664
1663
  if (section == AppSection.devices) {
@@ -2059,6 +2059,7 @@ class ModelMeta {
2059
2059
  final bool available;
2060
2060
  final String providerStatus;
2061
2061
  final String providerStatusLabel;
2062
+
2062
2063
  /// Pricing tier: 'free' | 'cheap' | 'medium' | 'expensive' | null (unknown)
2063
2064
  final String? priceTier;
2064
2065
  }
@@ -2308,18 +2309,33 @@ class TokenUsageSnapshot {
2308
2309
  required this.avgTokensPerRun,
2309
2310
  required this.last7DaysTokens,
2310
2311
  required this.last7DaysRuns,
2312
+ required this.cachedReadTokens,
2313
+ required this.cacheWriteTokens,
2314
+ required this.reasoningTokens,
2315
+ required this.modelCallCount,
2316
+ required this.cacheHitRatio,
2317
+ this.estimatedCostUsd,
2311
2318
  });
2312
2319
 
2313
2320
  factory TokenUsageSnapshot.fromJson(Map<dynamic, dynamic> json) {
2314
2321
  final totals = json['totals'] is Map
2315
2322
  ? Map<String, dynamic>.from(json['totals'] as Map)
2316
2323
  : const <String, dynamic>{};
2324
+ final modelUsage = json['modelUsage'] is Map
2325
+ ? Map<String, dynamic>.from(json['modelUsage'] as Map)
2326
+ : const <String, dynamic>{};
2317
2327
  return TokenUsageSnapshot(
2318
2328
  totalTokens: _asInt(totals['totalTokens']),
2319
2329
  totalRuns: _asInt(totals['totalRuns']),
2320
2330
  avgTokensPerRun: _asInt(totals['avgTokensPerRun']),
2321
2331
  last7DaysTokens: _asInt(totals['last7DaysTokens']),
2322
2332
  last7DaysRuns: _asInt(totals['last7DaysRuns']),
2333
+ cachedReadTokens: _asInt(modelUsage['cachedReadTokens']),
2334
+ cacheWriteTokens: _asInt(modelUsage['cacheWriteTokens']),
2335
+ reasoningTokens: _asInt(modelUsage['reasoningTokens']),
2336
+ modelCallCount: _asInt(modelUsage['callCount']),
2337
+ cacheHitRatio: (modelUsage['cacheHitRatio'] as num?)?.toDouble() ?? 0,
2338
+ estimatedCostUsd: (modelUsage['estimatedCostUsd'] as num?)?.toDouble(),
2323
2339
  );
2324
2340
  }
2325
2341
 
@@ -2328,12 +2344,24 @@ class TokenUsageSnapshot {
2328
2344
  final int avgTokensPerRun;
2329
2345
  final int last7DaysTokens;
2330
2346
  final int last7DaysRuns;
2347
+ final int cachedReadTokens;
2348
+ final int cacheWriteTokens;
2349
+ final int reasoningTokens;
2350
+ final int modelCallCount;
2351
+ final double cacheHitRatio;
2352
+ final double? estimatedCostUsd;
2331
2353
 
2332
2354
  String get totalTokensLabel => _formatNumber(totalTokens);
2333
2355
  String get totalRunsLabel => _formatNumber(totalRuns);
2334
2356
  String get avgTokensPerRunLabel => _formatNumber(avgTokensPerRun);
2335
2357
  String get last7DaysTokensLabel => _formatNumber(last7DaysTokens);
2336
2358
  String get last7DaysRunsLabel => _formatNumber(last7DaysRuns);
2359
+ String get cachedReadTokensLabel => _formatNumber(cachedReadTokens);
2360
+ String get cacheHitRatioLabel =>
2361
+ '${(cacheHitRatio * 100).toStringAsFixed(1)}%';
2362
+ String get estimatedCostLabel => estimatedCostUsd == null
2363
+ ? 'Unknown'
2364
+ : '\$${estimatedCostUsd!.toStringAsFixed(4)}';
2337
2365
  }
2338
2366
 
2339
2367
  class UpdateStatusSnapshot {
@@ -5629,7 +5629,7 @@ class _TasksPanelState extends State<TasksPanel> {
5629
5629
  );
5630
5630
  }
5631
5631
  return DropdownButtonFormField<int>(
5632
- value: accounts.any((a) => a.id == selectedConnectionId.value)
5632
+ initialValue: accounts.any((a) => a.id == selectedConnectionId.value)
5633
5633
  ? selectedConnectionId.value
5634
5634
  : null,
5635
5635
  isExpanded: true,
@@ -489,20 +489,6 @@ class _SettingsPanelState extends State<SettingsPanel> {
489
489
  );
490
490
  }
491
491
 
492
- Widget _inlineProgressIndicator() {
493
- return SizedBox(
494
- width: 28,
495
- child: ClipRRect(
496
- borderRadius: BorderRadius.circular(999),
497
- child: LinearProgressIndicator(
498
- minHeight: 3,
499
- backgroundColor: Colors.white.withValues(alpha: 0.28),
500
- color: Colors.white,
501
- ),
502
- ),
503
- );
504
- }
505
-
506
492
  Widget _buildSettingsOverview(
507
493
  NeoAgentController controller,
508
494
  int availableModelCount,
@@ -1546,6 +1532,15 @@ class _SettingsPanelState extends State<SettingsPanel> {
1546
1532
  Text(
1547
1533
  'Avg/run: ${controller.tokenUsage!.avgTokensPerRunLabel} tokens',
1548
1534
  ),
1535
+ const SizedBox(height: 6),
1536
+ Text(
1537
+ 'Prompt cache: ${controller.tokenUsage!.cachedReadTokensLabel} cached tokens '
1538
+ '(${controller.tokenUsage!.cacheHitRatioLabel} hit ratio)',
1539
+ ),
1540
+ const SizedBox(height: 6),
1541
+ Text(
1542
+ 'Measured model cost: ${controller.tokenUsage!.estimatedCostLabel}',
1543
+ ),
1549
1544
  ],
1550
1545
  ),
1551
1546
  ],
@@ -3440,15 +3440,6 @@ String _modelLabelForValue(String value, List<ModelMeta> models) {
3440
3440
  return value;
3441
3441
  }
3442
3442
 
3443
- String _friendlyBaseUrlLabel(String value) {
3444
- final uri = Uri.tryParse(value);
3445
- if (uri == null || uri.host.trim().isEmpty) {
3446
- return value;
3447
- }
3448
- final port = uri.hasPort ? ':${uri.port}' : '';
3449
- return '${uri.host}$port';
3450
- }
3451
-
3452
3443
  String? _androidRuntimeVersionLabel(Map<String, dynamic> runtime) {
3453
3444
  final apiLevel = _asInt(runtime['apiLevel']);
3454
3445
  final systemImage = runtime['systemImage']?.toString().trim() ?? '';
@@ -2,8 +2,6 @@ part of 'main.dart';
2
2
 
3
3
  enum _ToolsPageTab { integrations, mcp, skills }
4
4
 
5
- enum _RunsPageTab { runs }
6
-
7
5
  enum _SettingsWorkspaceSection { app, account, security }
8
6
 
9
7
  class ToolsPanel extends StatefulWidget {
@@ -1055,6 +1055,14 @@ class BackendClient {
1055
1055
  return getList(baseUrl, '/api/skills');
1056
1056
  }
1057
1057
 
1058
+ Future<List<Map<String, dynamic>>> fetchSkillMetrics(String baseUrl) {
1059
+ return getList(baseUrl, '/api/skills/metrics/summary');
1060
+ }
1061
+
1062
+ Future<List<Map<String, dynamic>>> fetchSkillAudit(String baseUrl) {
1063
+ return getList(baseUrl, '/api/skills/audit/summary');
1064
+ }
1065
+
1058
1066
  Future<List<Map<String, dynamic>>> fetchSkillStore(String baseUrl) async {
1059
1067
  return getList(baseUrl, '/api/store');
1060
1068
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.4.2-beta.5",
3
+ "version": "2.4.2-beta.7",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "AGPL-3.0-only",
6
6
  "main": "server/index.js",
@@ -764,6 +764,112 @@ db.exec(`
764
764
  CREATE INDEX IF NOT EXISTS idx_agent_run_events_run ON agent_run_events(run_id, sequence_index, id);
765
765
  CREATE INDEX IF NOT EXISTS idx_agent_run_events_user ON agent_run_events(user_id, created_at DESC);
766
766
 
767
+ CREATE TABLE IF NOT EXISTS agent_model_usage (
768
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
769
+ run_id TEXT NOT NULL,
770
+ step_id TEXT,
771
+ user_id INTEGER NOT NULL,
772
+ agent_id TEXT,
773
+ provider TEXT NOT NULL,
774
+ model TEXT NOT NULL,
775
+ phase TEXT DEFAULT 'model_turn',
776
+ input_tokens INTEGER DEFAULT 0,
777
+ output_tokens INTEGER DEFAULT 0,
778
+ reasoning_tokens INTEGER DEFAULT 0,
779
+ cached_read_tokens INTEGER DEFAULT 0,
780
+ cache_write_tokens INTEGER DEFAULT 0,
781
+ total_tokens INTEGER DEFAULT 0,
782
+ estimated_cost_usd REAL,
783
+ latency_ms INTEGER DEFAULT 0,
784
+ metadata_json TEXT DEFAULT '{}',
785
+ created_at TEXT DEFAULT (datetime('now')),
786
+ FOREIGN KEY (run_id) REFERENCES agent_runs(id) ON DELETE CASCADE,
787
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
788
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL,
789
+ FOREIGN KEY (step_id) REFERENCES agent_steps(id) ON DELETE SET NULL
790
+ );
791
+
792
+ CREATE INDEX IF NOT EXISTS idx_agent_model_usage_run ON agent_model_usage(run_id, id);
793
+ CREATE INDEX IF NOT EXISTS idx_agent_model_usage_user ON agent_model_usage(user_id, created_at DESC);
794
+
795
+ CREATE TABLE IF NOT EXISTS workspace_code_index (
796
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
797
+ user_id INTEGER NOT NULL,
798
+ path TEXT NOT NULL,
799
+ symbol TEXT,
800
+ symbol_type TEXT,
801
+ start_line INTEGER,
802
+ end_line INTEGER,
803
+ content TEXT NOT NULL,
804
+ content_hash TEXT NOT NULL,
805
+ embedding TEXT,
806
+ updated_at TEXT DEFAULT (datetime('now')),
807
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
808
+ UNIQUE(user_id, path, symbol, start_line)
809
+ );
810
+
811
+ CREATE INDEX IF NOT EXISTS idx_workspace_code_index_user
812
+ ON workspace_code_index(user_id, path);
813
+
814
+ CREATE TABLE IF NOT EXISTS task_webhook_deliveries (
815
+ id TEXT PRIMARY KEY,
816
+ task_id INTEGER NOT NULL,
817
+ user_id INTEGER NOT NULL,
818
+ request_id TEXT NOT NULL,
819
+ payload_hash TEXT NOT NULL,
820
+ status TEXT NOT NULL DEFAULT 'accepted',
821
+ response_json TEXT DEFAULT '{}',
822
+ created_at TEXT DEFAULT (datetime('now')),
823
+ completed_at TEXT,
824
+ FOREIGN KEY (task_id) REFERENCES scheduled_tasks(id) ON DELETE CASCADE,
825
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
826
+ UNIQUE(task_id, request_id)
827
+ );
828
+
829
+ CREATE TABLE IF NOT EXISTS task_webhook_secrets (
830
+ task_id INTEGER PRIMARY KEY,
831
+ user_id INTEGER NOT NULL,
832
+ secret_encrypted TEXT NOT NULL,
833
+ secret_fingerprint TEXT NOT NULL,
834
+ rotated_at TEXT DEFAULT (datetime('now')),
835
+ created_at TEXT DEFAULT (datetime('now')),
836
+ FOREIGN KEY (task_id) REFERENCES scheduled_tasks(id) ON DELETE CASCADE,
837
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
838
+ );
839
+
840
+ CREATE INDEX IF NOT EXISTS idx_task_webhook_deliveries_task
841
+ ON task_webhook_deliveries(task_id, created_at DESC);
842
+
843
+ CREATE TABLE IF NOT EXISTS skill_metrics (
844
+ user_id INTEGER NOT NULL,
845
+ agent_id TEXT,
846
+ skill_name TEXT NOT NULL,
847
+ invocation_count INTEGER DEFAULT 0,
848
+ success_count INTEGER DEFAULT 0,
849
+ failure_count INTEGER DEFAULT 0,
850
+ correction_count INTEGER DEFAULT 0,
851
+ total_tokens INTEGER DEFAULT 0,
852
+ last_used_at TEXT,
853
+ metadata_json TEXT DEFAULT '{}',
854
+ updated_at TEXT DEFAULT (datetime('now')),
855
+ PRIMARY KEY (user_id, agent_id, skill_name),
856
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
857
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE SET NULL
858
+ );
859
+
860
+ CREATE TABLE IF NOT EXISTS skill_workflow_observations (
861
+ user_id INTEGER NOT NULL,
862
+ agent_id TEXT NOT NULL,
863
+ workflow_signature TEXT NOT NULL,
864
+ observation_count INTEGER DEFAULT 0,
865
+ latest_run_id TEXT,
866
+ first_observed_at TEXT DEFAULT (datetime('now')),
867
+ last_observed_at TEXT DEFAULT (datetime('now')),
868
+ PRIMARY KEY (user_id, agent_id, workflow_signature),
869
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
870
+ FOREIGN KEY (agent_id) REFERENCES agents(id) ON DELETE CASCADE
871
+ );
872
+
767
873
  CREATE TABLE IF NOT EXISTS agent_settings (
768
874
  id INTEGER PRIMARY KEY AUTOINCREMENT,
769
875
  user_id INTEGER NOT NULL,
@@ -1159,6 +1265,12 @@ for (const col of [
1159
1265
  "ALTER TABLE desktop_companion_devices ADD COLUMN last_seen_at TEXT",
1160
1266
  "ALTER TABLE desktop_companion_devices ADD COLUMN revoked_at TEXT",
1161
1267
  "ALTER TABLE desktop_companion_devices ADD COLUMN updated_at TEXT DEFAULT (datetime('now'))",
1268
+ "ALTER TABLE memory_facts ADD COLUMN valid_from TEXT",
1269
+ "ALTER TABLE memory_facts ADD COLUMN valid_to TEXT",
1270
+ "ALTER TABLE memory_facts ADD COLUMN learned_at TEXT",
1271
+ "ALTER TABLE memory_facts ADD COLUMN invalidated_at TEXT",
1272
+ "ALTER TABLE memory_facts ADD COLUMN status TEXT DEFAULT 'active'",
1273
+ "ALTER TABLE memory_facts ADD COLUMN supersedes_fact_id TEXT",
1162
1274
  ]) {
1163
1275
  try { db.exec(col); } catch { /* column already exists */ }
1164
1276
  }
@@ -21,6 +21,7 @@ const routeRegistry = [
21
21
  { basePath: '/api/workspace', modulePath: '../routes/workspace' },
22
22
  { basePath: '/api/memory', modulePath: '../routes/memory' },
23
23
  { basePath: '/api/tasks', modulePath: '../routes/tasks' },
24
+ { basePath: '/api/task-webhooks', modulePath: '../routes/task_webhooks' },
24
25
  { basePath: '/api/widgets', modulePath: '../routes/widgets' },
25
26
  { basePath: '/api/browser', modulePath: '../routes/browser' },
26
27
  { basePath: '/api/browser-extension', modulePath: '../routes/browser_extension' },
@@ -1 +1 @@
1
- 15d41acc002ea2eaaa9deb2490573dd5
1
+ ec945bff38eacd619d4434e746ae61aa
@@ -33,10 +33,10 @@ addEventListener("message", eventListener);
33
33
  if (!window._flutter) {
34
34
  window._flutter = {};
35
35
  }
36
- _flutter.buildConfig = {"engineRevision":"c416acfeb8126e097f758c664aaa3da929e27da0","builds":[{"compileTarget":"dart2js","renderer":"canvaskit","mainJsPath":"main.dart.js"},{}]};
36
+ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19cab","builds":[{"compileTarget":"dart2js","renderer":"canvaskit","mainJsPath":"main.dart.js"},{}]};
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "3772976219" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "1556394997" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });