@plures/runebook 0.4.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.
- package/ANALYSIS_LADDER.md +231 -0
- package/CHANGELOG.md +124 -0
- package/INTEGRATIONS.md +242 -0
- package/LICENSE +21 -0
- package/MEMORY.md +253 -0
- package/NIXOS.md +357 -0
- package/QUICKSTART.md +157 -0
- package/README.md +295 -0
- package/RELEASE.md +190 -0
- package/ValidationChecklist.md +598 -0
- package/docs/demo.md +338 -0
- package/docs/llm-integration.md +300 -0
- package/docs/parallel-execution-plan.md +160 -0
- package/flake.nix +228 -0
- package/integrations/README.md +242 -0
- package/integrations/demo-steps.sh +64 -0
- package/integrations/nvim-runebook.lua +140 -0
- package/integrations/tmux-status.sh +51 -0
- package/integrations/vim-runebook.vim +77 -0
- package/integrations/wezterm-status-simple.lua +48 -0
- package/integrations/wezterm-status.lua +76 -0
- package/nixos-module.nix +156 -0
- package/package.json +76 -0
- package/packages/design-dojo/index.js +4 -0
- package/packages/design-dojo/package.json +20 -0
- package/packages/design-dojo/tokens.css +69 -0
- package/playwright.config.ts +16 -0
- package/scripts/check-versions.cjs +62 -0
- package/scripts/demo.sh +220 -0
- package/shell.nix +31 -0
- package/src/app.html +13 -0
- package/src/cli/index.ts +1050 -0
- package/src/lib/agent/analysis-pipeline.ts +347 -0
- package/src/lib/agent/analysis-service.ts +171 -0
- package/src/lib/agent/analysis.ts +159 -0
- package/src/lib/agent/analyzers/heuristic.ts +289 -0
- package/src/lib/agent/analyzers/index.ts +7 -0
- package/src/lib/agent/analyzers/llm.ts +204 -0
- package/src/lib/agent/analyzers/local-search.ts +215 -0
- package/src/lib/agent/capture.ts +123 -0
- package/src/lib/agent/index.ts +244 -0
- package/src/lib/agent/integration.ts +81 -0
- package/src/lib/agent/llm/providers/base.ts +99 -0
- package/src/lib/agent/llm/providers/index.ts +60 -0
- package/src/lib/agent/llm/providers/mock.ts +67 -0
- package/src/lib/agent/llm/providers/ollama.ts +151 -0
- package/src/lib/agent/llm/providers/openai.ts +153 -0
- package/src/lib/agent/llm/sanitizer.ts +170 -0
- package/src/lib/agent/llm/types.ts +118 -0
- package/src/lib/agent/memory.ts +363 -0
- package/src/lib/agent/node-status.ts +56 -0
- package/src/lib/agent/node-suggestions.ts +64 -0
- package/src/lib/agent/status.ts +80 -0
- package/src/lib/agent/suggestions.ts +169 -0
- package/src/lib/components/Canvas.svelte +124 -0
- package/src/lib/components/ConnectionLine.svelte +46 -0
- package/src/lib/components/DisplayNode.svelte +167 -0
- package/src/lib/components/InputNode.svelte +158 -0
- package/src/lib/components/TerminalNode.svelte +237 -0
- package/src/lib/components/Toolbar.svelte +359 -0
- package/src/lib/components/TransformNode.svelte +327 -0
- package/src/lib/core/index.ts +31 -0
- package/src/lib/core/observer.ts +278 -0
- package/src/lib/core/redaction.ts +158 -0
- package/src/lib/core/shell-adapters/base.ts +325 -0
- package/src/lib/core/shell-adapters/bash.ts +110 -0
- package/src/lib/core/shell-adapters/index.ts +62 -0
- package/src/lib/core/shell-adapters/zsh.ts +105 -0
- package/src/lib/core/storage.ts +360 -0
- package/src/lib/core/types.ts +176 -0
- package/src/lib/design-dojo/Box.svelte +47 -0
- package/src/lib/design-dojo/Button.svelte +75 -0
- package/src/lib/design-dojo/Input.svelte +65 -0
- package/src/lib/design-dojo/List.svelte +38 -0
- package/src/lib/design-dojo/Select.svelte +48 -0
- package/src/lib/design-dojo/SplitPane.svelte +43 -0
- package/src/lib/design-dojo/StatusBar.svelte +61 -0
- package/src/lib/design-dojo/Table.svelte +47 -0
- package/src/lib/design-dojo/Text.svelte +36 -0
- package/src/lib/design-dojo/Toggle.svelte +48 -0
- package/src/lib/design-dojo/index.ts +10 -0
- package/src/lib/stores/canvas-praxis.ts +268 -0
- package/src/lib/stores/canvas.ts +58 -0
- package/src/lib/types/agent.ts +78 -0
- package/src/lib/types/canvas.ts +71 -0
- package/src/lib/utils/storage.ts +326 -0
- package/src/lib/utils/yaml-loader.ts +52 -0
- package/src/routes/+layout.svelte +5 -0
- package/src/routes/+layout.ts +5 -0
- package/src/routes/+page.svelte +32 -0
- package/src-tauri/Cargo.lock +5735 -0
- package/src-tauri/Cargo.toml +38 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +10 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/agents/agent1.rs +66 -0
- package/src-tauri/src/agents/agent2.rs +80 -0
- package/src-tauri/src/agents/agent3.rs +73 -0
- package/src-tauri/src/agents/agent4.rs +66 -0
- package/src-tauri/src/agents/agent5.rs +68 -0
- package/src-tauri/src/agents/agent6.rs +75 -0
- package/src-tauri/src/agents/base.rs +52 -0
- package/src-tauri/src/agents/mod.rs +17 -0
- package/src-tauri/src/core/coordination.rs +117 -0
- package/src-tauri/src/core/mod.rs +12 -0
- package/src-tauri/src/core/ownership.rs +61 -0
- package/src-tauri/src/core/types.rs +132 -0
- package/src-tauri/src/execution/mod.rs +5 -0
- package/src-tauri/src/execution/runner.rs +143 -0
- package/src-tauri/src/lib.rs +161 -0
- package/src-tauri/src/main.rs +6 -0
- package/src-tauri/src/memory/api.rs +422 -0
- package/src-tauri/src/memory/client.rs +156 -0
- package/src-tauri/src/memory/encryption.rs +79 -0
- package/src-tauri/src/memory/migration.rs +110 -0
- package/src-tauri/src/memory/mod.rs +28 -0
- package/src-tauri/src/memory/schema.rs +275 -0
- package/src-tauri/src/memory/tests.rs +192 -0
- package/src-tauri/src/orchestrator/coordinator.rs +232 -0
- package/src-tauri/src/orchestrator/mod.rs +13 -0
- package/src-tauri/src/orchestrator/planner.rs +304 -0
- package/src-tauri/tauri.conf.json +35 -0
- package/static/examples/date-time-example.yaml +147 -0
- package/static/examples/hello-world.yaml +74 -0
- package/static/examples/transform-example.yaml +157 -0
- package/static/favicon.png +0 -0
- package/static/svelte.svg +1 -0
- package/static/tauri.svg +6 -0
- package/static/vite.svg +1 -0
- package/svelte.config.js +18 -0
- package/tsconfig.json +19 -0
- package/vite.config.js +45 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//! Coordination mechanisms for agent communication.
|
|
2
|
+
|
|
3
|
+
use super::types::{AgentId, ApiPublished, CoordinationMessage};
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
use tokio::sync::mpsc;
|
|
6
|
+
|
|
7
|
+
/// Coordination channel for agent communication
|
|
8
|
+
pub struct CoordinationChannel {
|
|
9
|
+
sender: mpsc::UnboundedSender<CoordinationMessage>,
|
|
10
|
+
receiver: mpsc::UnboundedReceiver<CoordinationMessage>,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl CoordinationChannel {
|
|
14
|
+
pub fn new() -> (Self, CoordinationHandle) {
|
|
15
|
+
let (sender, receiver) = mpsc::unbounded_channel();
|
|
16
|
+
let channel = Self { sender, receiver };
|
|
17
|
+
let handle = CoordinationHandle {
|
|
18
|
+
sender: channel.sender.clone(),
|
|
19
|
+
};
|
|
20
|
+
(channel, handle)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub async fn recv(&mut self) -> Option<CoordinationMessage> {
|
|
24
|
+
self.receiver.recv().await
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub fn try_recv(&mut self) -> Option<CoordinationMessage> {
|
|
28
|
+
self.receiver.try_recv().ok()
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Handle for sending coordination messages
|
|
33
|
+
#[derive(Clone)]
|
|
34
|
+
pub struct CoordinationHandle {
|
|
35
|
+
sender: mpsc::UnboundedSender<CoordinationMessage>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl CoordinationHandle {
|
|
39
|
+
pub fn send(&self, message: CoordinationMessage) -> Result<(), String> {
|
|
40
|
+
self.sender
|
|
41
|
+
.send(message)
|
|
42
|
+
.map_err(|e| format!("Channel closed: {}", e))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
pub fn agent_ready(&self, agent: AgentId) -> Result<(), String> {
|
|
46
|
+
self.send(CoordinationMessage::AgentReady(agent))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn api_published(&self, api: ApiPublished) -> Result<(), String> {
|
|
50
|
+
self.send(CoordinationMessage::ApiPublished(api))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn task_completed(&self, agent: AgentId, task_id: String) -> Result<(), String> {
|
|
54
|
+
self.send(CoordinationMessage::TaskCompleted(agent, task_id))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub fn status_update(
|
|
58
|
+
&self,
|
|
59
|
+
agent: AgentId,
|
|
60
|
+
status: super::types::AgentStatus,
|
|
61
|
+
) -> Result<(), String> {
|
|
62
|
+
self.send(CoordinationMessage::StatusUpdate(agent, status))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pub fn request_coordination(
|
|
66
|
+
&self,
|
|
67
|
+
requester: AgentId,
|
|
68
|
+
target_agent: AgentId,
|
|
69
|
+
target_module: String,
|
|
70
|
+
reason: String,
|
|
71
|
+
) -> Result<(), String> {
|
|
72
|
+
self.send(CoordinationMessage::CoordinationRequest {
|
|
73
|
+
requester,
|
|
74
|
+
target_agent,
|
|
75
|
+
target_module,
|
|
76
|
+
reason,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// API registry for tracking published APIs
|
|
82
|
+
pub struct ApiRegistry {
|
|
83
|
+
apis: HashMap<String, ApiPublished>,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
impl ApiRegistry {
|
|
87
|
+
pub fn new() -> Self {
|
|
88
|
+
Self {
|
|
89
|
+
apis: HashMap::new(),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn register(&mut self, api: ApiPublished) {
|
|
94
|
+
self.apis.insert(api.api_name.clone(), api);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
pub fn is_published(&self, api_name: &str) -> bool {
|
|
98
|
+
self.apis.contains_key(api_name)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pub fn get_api(&self, api_name: &str) -> Option<&ApiPublished> {
|
|
102
|
+
self.apis.get(api_name)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub fn get_agent_apis(&self, agent: AgentId) -> Vec<&ApiPublished> {
|
|
106
|
+
self.apis
|
|
107
|
+
.values()
|
|
108
|
+
.filter(|api| api.agent == agent)
|
|
109
|
+
.collect()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
impl Default for ApiRegistry {
|
|
114
|
+
fn default() -> Self {
|
|
115
|
+
Self::new()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//! Shared types and interfaces for the RuneBook parallel execution system.
|
|
2
|
+
//!
|
|
3
|
+
//! This module contains types that are shared across all agents and the orchestrator.
|
|
4
|
+
//! All shared types should be defined here to avoid circular dependencies.
|
|
5
|
+
|
|
6
|
+
pub mod coordination;
|
|
7
|
+
pub mod ownership;
|
|
8
|
+
pub mod types;
|
|
9
|
+
|
|
10
|
+
pub use coordination::*;
|
|
11
|
+
pub use ownership::*;
|
|
12
|
+
pub use types::*;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//! File ownership boundaries and coordination rules.
|
|
2
|
+
|
|
3
|
+
use super::types::{AgentId, FileOwnership};
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
|
|
6
|
+
/// Manages file ownership boundaries
|
|
7
|
+
pub struct OwnershipManager {
|
|
8
|
+
ownership_map: HashMap<String, FileOwnership>,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
impl OwnershipManager {
|
|
12
|
+
pub fn new() -> Self {
|
|
13
|
+
Self {
|
|
14
|
+
ownership_map: HashMap::new(),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// Register file ownership
|
|
19
|
+
pub fn register(&mut self, ownership: FileOwnership) {
|
|
20
|
+
self.ownership_map.insert(ownership.path.clone(), ownership);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Check if an agent can modify a file
|
|
24
|
+
pub fn can_modify(&self, agent: AgentId, path: &str) -> bool {
|
|
25
|
+
if let Some(ownership) = self.ownership_map.get(path) {
|
|
26
|
+
ownership.owner == agent || (ownership.shared && ownership.owner == agent)
|
|
27
|
+
} else {
|
|
28
|
+
// If not registered, allow (for now - orchestrator should register all)
|
|
29
|
+
true
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Check if an agent can read a file
|
|
34
|
+
pub fn can_read(&self, _agent: AgentId, path: &str) -> bool {
|
|
35
|
+
// All agents can read shared files
|
|
36
|
+
if let Some(ownership) = self.ownership_map.get(path) {
|
|
37
|
+
ownership.shared || true // For now, allow all reads
|
|
38
|
+
} else {
|
|
39
|
+
true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Get owner of a file
|
|
44
|
+
pub fn get_owner(&self, path: &str) -> Option<AgentId> {
|
|
45
|
+
self.ownership_map.get(path).map(|o| o.owner)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Get all files owned by an agent
|
|
49
|
+
pub fn get_agent_files(&self, agent: AgentId) -> Vec<&FileOwnership> {
|
|
50
|
+
self.ownership_map
|
|
51
|
+
.values()
|
|
52
|
+
.filter(|o| o.owner == agent)
|
|
53
|
+
.collect()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
impl Default for OwnershipManager {
|
|
58
|
+
fn default() -> Self {
|
|
59
|
+
Self::new()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//! Shared types for the parallel execution system.
|
|
2
|
+
|
|
3
|
+
use serde::{Deserialize, Serialize};
|
|
4
|
+
use std::collections::HashMap;
|
|
5
|
+
|
|
6
|
+
/// Agent identifier
|
|
7
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
8
|
+
pub enum AgentId {
|
|
9
|
+
Orchestrator,
|
|
10
|
+
Agent1, // Event capture
|
|
11
|
+
Agent2, // Storage APIs
|
|
12
|
+
Agent3, // Analysis pipeline
|
|
13
|
+
Agent4, // Surfaces
|
|
14
|
+
Agent5, // Nix + CI scaffolding
|
|
15
|
+
Agent6, // Finalization
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl AgentId {
|
|
19
|
+
pub fn name(&self) -> &'static str {
|
|
20
|
+
match self {
|
|
21
|
+
AgentId::Orchestrator => "orchestrator",
|
|
22
|
+
AgentId::Agent1 => "agent1-event-capture",
|
|
23
|
+
AgentId::Agent2 => "agent2-storage-apis",
|
|
24
|
+
AgentId::Agent3 => "agent3-analysis-pipeline",
|
|
25
|
+
AgentId::Agent4 => "agent4-surfaces",
|
|
26
|
+
AgentId::Agent5 => "agent5-nix-ci",
|
|
27
|
+
AgentId::Agent6 => "agent6-finalization",
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Agent execution status
|
|
33
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
34
|
+
pub enum AgentStatus {
|
|
35
|
+
Pending,
|
|
36
|
+
Running,
|
|
37
|
+
WaitingForDependency(AgentId),
|
|
38
|
+
Completed,
|
|
39
|
+
Failed(String),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Task breakdown item
|
|
43
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
44
|
+
pub struct Task {
|
|
45
|
+
pub id: String,
|
|
46
|
+
pub description: String,
|
|
47
|
+
pub owner: AgentId,
|
|
48
|
+
pub dependencies: Vec<AgentId>,
|
|
49
|
+
pub status: TaskStatus,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
53
|
+
pub enum TaskStatus {
|
|
54
|
+
NotStarted,
|
|
55
|
+
InProgress,
|
|
56
|
+
Completed,
|
|
57
|
+
Blocked(String),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Roadmap item
|
|
61
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
62
|
+
pub struct RoadmapItem {
|
|
63
|
+
pub phase: String,
|
|
64
|
+
pub description: String,
|
|
65
|
+
pub agents: Vec<AgentId>,
|
|
66
|
+
pub dependencies: Vec<String>, // Phase IDs
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Interface stub definition
|
|
70
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
71
|
+
pub struct InterfaceStub {
|
|
72
|
+
pub name: String,
|
|
73
|
+
pub module_path: String,
|
|
74
|
+
pub owner: AgentId,
|
|
75
|
+
pub signature: String,
|
|
76
|
+
pub description: String,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// File ownership boundary
|
|
80
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
81
|
+
pub struct FileOwnership {
|
|
82
|
+
pub path: String,
|
|
83
|
+
pub owner: AgentId,
|
|
84
|
+
pub description: String,
|
|
85
|
+
pub shared: bool, // If true, other agents can read but not modify
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// API publication event
|
|
89
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
90
|
+
pub struct ApiPublished {
|
|
91
|
+
pub agent: AgentId,
|
|
92
|
+
pub api_name: String,
|
|
93
|
+
pub interface_path: String,
|
|
94
|
+
pub version: String,
|
|
95
|
+
pub timestamp: chrono::DateTime<chrono::Utc>,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Coordination message
|
|
99
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
100
|
+
pub enum CoordinationMessage {
|
|
101
|
+
/// Agent is ready to start
|
|
102
|
+
AgentReady(AgentId),
|
|
103
|
+
/// Agent has published an API
|
|
104
|
+
ApiPublished(ApiPublished),
|
|
105
|
+
/// Agent has completed a task
|
|
106
|
+
TaskCompleted(AgentId, String), // agent, task_id
|
|
107
|
+
/// Agent needs coordination to modify another agent's module
|
|
108
|
+
CoordinationRequest {
|
|
109
|
+
requester: AgentId,
|
|
110
|
+
target_agent: AgentId,
|
|
111
|
+
target_module: String,
|
|
112
|
+
reason: String,
|
|
113
|
+
},
|
|
114
|
+
/// Orchestrator approval/rejection
|
|
115
|
+
CoordinationResponse {
|
|
116
|
+
request_id: String,
|
|
117
|
+
approved: bool,
|
|
118
|
+
reason: Option<String>,
|
|
119
|
+
},
|
|
120
|
+
/// Agent status update
|
|
121
|
+
StatusUpdate(AgentId, AgentStatus),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Execution plan
|
|
125
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
126
|
+
pub struct ExecutionPlan {
|
|
127
|
+
pub roadmap: Vec<RoadmapItem>,
|
|
128
|
+
pub tasks: Vec<Task>,
|
|
129
|
+
pub interfaces: Vec<InterfaceStub>,
|
|
130
|
+
pub file_ownership: Vec<FileOwnership>,
|
|
131
|
+
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
132
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
//! Parallel execution runner.
|
|
2
|
+
|
|
3
|
+
use crate::agents::*;
|
|
4
|
+
use crate::core::coordination::CoordinationHandle;
|
|
5
|
+
use crate::core::types::AgentId;
|
|
6
|
+
use crate::orchestrator::{create_execution_plan, ExecutionCoordinator};
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
use tokio::sync::{Mutex, RwLock};
|
|
9
|
+
|
|
10
|
+
/// Runs agents in parallel according to the execution plan
|
|
11
|
+
pub struct ParallelExecutionRunner {
|
|
12
|
+
coordinator: Arc<RwLock<ExecutionCoordinator>>,
|
|
13
|
+
coordination_handle: CoordinationHandle,
|
|
14
|
+
agent1: Arc<Mutex<Agent1>>,
|
|
15
|
+
agent2: Arc<Mutex<Agent2>>,
|
|
16
|
+
agent3: Arc<Mutex<Agent3>>,
|
|
17
|
+
agent4: Arc<Mutex<Agent4>>,
|
|
18
|
+
agent5: Arc<Mutex<Agent5>>,
|
|
19
|
+
agent6: Arc<Mutex<Agent6>>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl ParallelExecutionRunner {
|
|
23
|
+
pub fn new() -> (Self, CoordinationHandle) {
|
|
24
|
+
let plan = create_execution_plan();
|
|
25
|
+
let (coordinator, coordination_handle) = ExecutionCoordinator::new(plan);
|
|
26
|
+
let coordinator = Arc::new(RwLock::new(coordinator));
|
|
27
|
+
|
|
28
|
+
(
|
|
29
|
+
Self {
|
|
30
|
+
coordinator,
|
|
31
|
+
coordination_handle: coordination_handle.clone(),
|
|
32
|
+
agent1: Arc::new(Mutex::new(Agent1::new())),
|
|
33
|
+
agent2: Arc::new(Mutex::new(Agent2::new())),
|
|
34
|
+
agent3: Arc::new(Mutex::new(Agent3::new())),
|
|
35
|
+
agent4: Arc::new(Mutex::new(Agent4::new())),
|
|
36
|
+
agent5: Arc::new(Mutex::new(Agent5::new())),
|
|
37
|
+
agent6: Arc::new(Mutex::new(Agent6::new())),
|
|
38
|
+
},
|
|
39
|
+
coordination_handle,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// Execute all agents according to the parallel execution plan
|
|
44
|
+
pub async fn execute(&mut self) -> Result<(), String> {
|
|
45
|
+
log::info!("Starting parallel execution...");
|
|
46
|
+
|
|
47
|
+
// Phase 1: Orchestrator (already done via create_execution_plan)
|
|
48
|
+
log::info!("Phase 1: Orchestrator completed (roadmap, tasks, interfaces, ownership)");
|
|
49
|
+
|
|
50
|
+
// Phase 2: Agent 1 and Agent 2 run in parallel
|
|
51
|
+
log::info!("Phase 2: Starting Agent 1 and Agent 2 in parallel...");
|
|
52
|
+
let agent1_handle = {
|
|
53
|
+
let agent = Arc::clone(&self.agent1);
|
|
54
|
+
let handle = self.coordination_handle.clone();
|
|
55
|
+
tokio::spawn(async move {
|
|
56
|
+
let mut agent = agent.lock().await;
|
|
57
|
+
agent.initialize(handle.clone()).await?;
|
|
58
|
+
agent.execute().await
|
|
59
|
+
})
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let agent2_handle = {
|
|
63
|
+
let agent = Arc::clone(&self.agent2);
|
|
64
|
+
let handle = self.coordination_handle.clone();
|
|
65
|
+
tokio::spawn(async move {
|
|
66
|
+
let mut agent = agent.lock().await;
|
|
67
|
+
agent.initialize(handle.clone()).await?;
|
|
68
|
+
agent.execute().await
|
|
69
|
+
})
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Wait for both to complete
|
|
73
|
+
let (result1, result2) = tokio::join!(agent1_handle, agent2_handle);
|
|
74
|
+
result1.map_err(|e| format!("Agent 1 error: {:?}", e))??;
|
|
75
|
+
result2.map_err(|e| format!("Agent 2 error: {:?}", e))??;
|
|
76
|
+
|
|
77
|
+
// Process coordination messages
|
|
78
|
+
self.coordinator
|
|
79
|
+
.write()
|
|
80
|
+
.await
|
|
81
|
+
.process_coordination()
|
|
82
|
+
.await?;
|
|
83
|
+
|
|
84
|
+
// Phase 3: Agent 3 starts after Agent 2 publishes APIs
|
|
85
|
+
log::info!("Phase 3: Starting Agent 3 (after Agent 2 APIs published)...");
|
|
86
|
+
{
|
|
87
|
+
let mut agent = self.agent3.lock().await;
|
|
88
|
+
agent.initialize(self.coordination_handle.clone()).await?;
|
|
89
|
+
agent.execute().await?;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Process coordination messages
|
|
93
|
+
self.coordinator
|
|
94
|
+
.write()
|
|
95
|
+
.await
|
|
96
|
+
.process_coordination()
|
|
97
|
+
.await?;
|
|
98
|
+
|
|
99
|
+
// Phase 4: Agent 4 starts after Agent 3 writes suggestions
|
|
100
|
+
log::info!("Phase 4: Starting Agent 4 (after Agent 3 writes suggestions)...");
|
|
101
|
+
{
|
|
102
|
+
let mut agent = self.agent4.lock().await;
|
|
103
|
+
agent.initialize(self.coordination_handle.clone()).await?;
|
|
104
|
+
agent.execute().await?;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Phase 5: Agent 5 and Agent 6 run continuously
|
|
108
|
+
log::info!("Phase 5: Starting Agent 5 and Agent 6 (continuous)...");
|
|
109
|
+
let agent5_handle = {
|
|
110
|
+
let agent = Arc::clone(&self.agent5);
|
|
111
|
+
let handle = self.coordination_handle.clone();
|
|
112
|
+
tokio::spawn(async move {
|
|
113
|
+
let mut agent = agent.lock().await;
|
|
114
|
+
agent.initialize(handle.clone()).await?;
|
|
115
|
+
agent.execute().await
|
|
116
|
+
})
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
let agent6_handle = {
|
|
120
|
+
let agent = Arc::clone(&self.agent6);
|
|
121
|
+
let handle = self.coordination_handle.clone();
|
|
122
|
+
tokio::spawn(async move {
|
|
123
|
+
let mut agent = agent.lock().await;
|
|
124
|
+
agent.initialize(handle.clone()).await?;
|
|
125
|
+
agent.execute().await
|
|
126
|
+
})
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Wait for continuous agents (they run in background)
|
|
130
|
+
let (result5, result6) = tokio::join!(agent5_handle, agent6_handle);
|
|
131
|
+
result5.map_err(|e| format!("Agent 5 error: {:?}", e))??;
|
|
132
|
+
result6.map_err(|e| format!("Agent 6 error: {:?}", e))??;
|
|
133
|
+
|
|
134
|
+
// Finalize Agent 6
|
|
135
|
+
{
|
|
136
|
+
let mut agent = self.agent6.lock().await;
|
|
137
|
+
agent.finalize().await?;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
log::info!("Parallel execution completed!");
|
|
141
|
+
Ok(())
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
pub mod agents;
|
|
2
|
+
pub mod core;
|
|
3
|
+
pub mod execution;
|
|
4
|
+
pub mod memory;
|
|
5
|
+
pub mod orchestrator;
|
|
6
|
+
|
|
7
|
+
use std::collections::HashMap;
|
|
8
|
+
use std::process::Command;
|
|
9
|
+
|
|
10
|
+
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
|
11
|
+
#[tauri::command]
|
|
12
|
+
fn greet(name: &str) -> String {
|
|
13
|
+
format!("Hello, {}! You've been greeted from Rust!", name)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[tauri::command]
|
|
17
|
+
async fn memory_inspect(
|
|
18
|
+
host: Option<String>,
|
|
19
|
+
port: Option<u16>,
|
|
20
|
+
data_dir: Option<String>,
|
|
21
|
+
) -> Result<String, String> {
|
|
22
|
+
use crate::memory::*;
|
|
23
|
+
|
|
24
|
+
let host = host.as_deref().unwrap_or("localhost");
|
|
25
|
+
let port = port.unwrap_or(34567);
|
|
26
|
+
let data_dir = data_dir.as_deref().unwrap_or("./pluresdb-data");
|
|
27
|
+
|
|
28
|
+
match init_memory_store(host, port, data_dir).await {
|
|
29
|
+
Ok(store) => {
|
|
30
|
+
let sessions = store
|
|
31
|
+
.list_sessions()
|
|
32
|
+
.await
|
|
33
|
+
.map_err(|e| format!("Failed to list sessions: {}", e))?;
|
|
34
|
+
let errors = store
|
|
35
|
+
.query_recent_errors(Some(10), None, None)
|
|
36
|
+
.await
|
|
37
|
+
.map_err(|e| format!("Failed to query errors: {}", e))?;
|
|
38
|
+
let suggestions = store
|
|
39
|
+
.get_suggestions(None, Some(10))
|
|
40
|
+
.await
|
|
41
|
+
.map_err(|e| format!("Failed to get suggestions: {}", e))?;
|
|
42
|
+
|
|
43
|
+
let mut output = String::new();
|
|
44
|
+
output.push_str("=== RuneBook Cognitive Memory ===\n\n");
|
|
45
|
+
output.push_str(&format!("Sessions: {}\n", sessions.len()));
|
|
46
|
+
output.push_str(&format!("Recent Errors: {}\n", errors.len()));
|
|
47
|
+
output.push_str(&format!("Active Suggestions: {}\n\n", suggestions.len()));
|
|
48
|
+
|
|
49
|
+
if !sessions.is_empty() {
|
|
50
|
+
output.push_str("=== Recent Sessions ===\n");
|
|
51
|
+
for session in sessions.iter().take(5) {
|
|
52
|
+
output.push_str(&format!(
|
|
53
|
+
" {} - {} (started: {})\n",
|
|
54
|
+
session.id,
|
|
55
|
+
session.shell_type,
|
|
56
|
+
session.started_at.format("%Y-%m-%d %H:%M:%S")
|
|
57
|
+
));
|
|
58
|
+
}
|
|
59
|
+
output.push_str("\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if !errors.is_empty() {
|
|
63
|
+
output.push_str("=== Recent Errors ===\n");
|
|
64
|
+
for error in errors.iter().take(5) {
|
|
65
|
+
output.push_str(&format!(
|
|
66
|
+
" [{}] {} - {}\n",
|
|
67
|
+
error.severity, error.error_type, error.message
|
|
68
|
+
));
|
|
69
|
+
}
|
|
70
|
+
output.push_str("\n");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if !suggestions.is_empty() {
|
|
74
|
+
output.push_str("=== Top Suggestions ===\n");
|
|
75
|
+
for suggestion in suggestions.iter().take(5) {
|
|
76
|
+
output.push_str(&format!(
|
|
77
|
+
" [{}] {} - {}\n",
|
|
78
|
+
suggestion.priority, suggestion.title, suggestion.description
|
|
79
|
+
));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Ok(output)
|
|
84
|
+
}
|
|
85
|
+
Err(e) => Err(format!("Failed to initialize memory store: {}", e)),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[tauri::command]
|
|
90
|
+
async fn execute_terminal_command(
|
|
91
|
+
command: String,
|
|
92
|
+
args: Vec<String>,
|
|
93
|
+
env: HashMap<String, String>,
|
|
94
|
+
cwd: String,
|
|
95
|
+
) -> Result<String, String> {
|
|
96
|
+
// Basic input validation to prevent common issues
|
|
97
|
+
if command.trim().is_empty() {
|
|
98
|
+
return Err("Command cannot be empty".to_string());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Prevent command chaining attacks via common shell operators
|
|
102
|
+
let dangerous_chars = ['|', ';', '&', '>', '<', '`', '$', '(', ')'];
|
|
103
|
+
if command.chars().any(|c| dangerous_chars.contains(&c)) {
|
|
104
|
+
return Err("Command contains potentially dangerous characters. RuneBook executes commands directly without shell interpretation for security.".to_string());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Validate environment variable keys (no special chars)
|
|
108
|
+
for key in env.keys() {
|
|
109
|
+
if !key.chars().all(|c| c.is_alphanumeric() || c == '_') {
|
|
110
|
+
return Err(format!("Invalid environment variable name: {}", key));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Note: This executes the command directly without a shell, which prevents
|
|
115
|
+
// shell injection attacks. Command::new does not interpret shell syntax.
|
|
116
|
+
let mut cmd = Command::new(&command);
|
|
117
|
+
|
|
118
|
+
// Add arguments - each arg is passed as-is, not interpreted by a shell
|
|
119
|
+
cmd.args(&args);
|
|
120
|
+
|
|
121
|
+
// Add environment variables
|
|
122
|
+
for (key, value) in env {
|
|
123
|
+
cmd.env(key, value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Set working directory if provided
|
|
127
|
+
if !cwd.is_empty() {
|
|
128
|
+
cmd.current_dir(&cwd);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Execute command
|
|
132
|
+
match cmd.output() {
|
|
133
|
+
Ok(output) => {
|
|
134
|
+
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
|
135
|
+
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
|
|
136
|
+
|
|
137
|
+
if output.status.success() {
|
|
138
|
+
Ok(stdout)
|
|
139
|
+
} else {
|
|
140
|
+
Err(format!("Command failed: {}\n{}", stderr, stdout))
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
Err(e) => Err(format!("Failed to execute command: {}", e)),
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
148
|
+
pub fn run() {
|
|
149
|
+
// Initialize logger (ignore error if already initialized)
|
|
150
|
+
let _ = env_logger::try_init();
|
|
151
|
+
|
|
152
|
+
tauri::Builder::default()
|
|
153
|
+
.plugin(tauri_plugin_opener::init())
|
|
154
|
+
.invoke_handler(tauri::generate_handler![
|
|
155
|
+
greet,
|
|
156
|
+
execute_terminal_command,
|
|
157
|
+
memory_inspect
|
|
158
|
+
])
|
|
159
|
+
.run(tauri::generate_context!())
|
|
160
|
+
.expect("error while running tauri application");
|
|
161
|
+
}
|