@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.
Files changed (148) hide show
  1. package/ANALYSIS_LADDER.md +231 -0
  2. package/CHANGELOG.md +124 -0
  3. package/INTEGRATIONS.md +242 -0
  4. package/LICENSE +21 -0
  5. package/MEMORY.md +253 -0
  6. package/NIXOS.md +357 -0
  7. package/QUICKSTART.md +157 -0
  8. package/README.md +295 -0
  9. package/RELEASE.md +190 -0
  10. package/ValidationChecklist.md +598 -0
  11. package/docs/demo.md +338 -0
  12. package/docs/llm-integration.md +300 -0
  13. package/docs/parallel-execution-plan.md +160 -0
  14. package/flake.nix +228 -0
  15. package/integrations/README.md +242 -0
  16. package/integrations/demo-steps.sh +64 -0
  17. package/integrations/nvim-runebook.lua +140 -0
  18. package/integrations/tmux-status.sh +51 -0
  19. package/integrations/vim-runebook.vim +77 -0
  20. package/integrations/wezterm-status-simple.lua +48 -0
  21. package/integrations/wezterm-status.lua +76 -0
  22. package/nixos-module.nix +156 -0
  23. package/package.json +76 -0
  24. package/packages/design-dojo/index.js +4 -0
  25. package/packages/design-dojo/package.json +20 -0
  26. package/packages/design-dojo/tokens.css +69 -0
  27. package/playwright.config.ts +16 -0
  28. package/scripts/check-versions.cjs +62 -0
  29. package/scripts/demo.sh +220 -0
  30. package/shell.nix +31 -0
  31. package/src/app.html +13 -0
  32. package/src/cli/index.ts +1050 -0
  33. package/src/lib/agent/analysis-pipeline.ts +347 -0
  34. package/src/lib/agent/analysis-service.ts +171 -0
  35. package/src/lib/agent/analysis.ts +159 -0
  36. package/src/lib/agent/analyzers/heuristic.ts +289 -0
  37. package/src/lib/agent/analyzers/index.ts +7 -0
  38. package/src/lib/agent/analyzers/llm.ts +204 -0
  39. package/src/lib/agent/analyzers/local-search.ts +215 -0
  40. package/src/lib/agent/capture.ts +123 -0
  41. package/src/lib/agent/index.ts +244 -0
  42. package/src/lib/agent/integration.ts +81 -0
  43. package/src/lib/agent/llm/providers/base.ts +99 -0
  44. package/src/lib/agent/llm/providers/index.ts +60 -0
  45. package/src/lib/agent/llm/providers/mock.ts +67 -0
  46. package/src/lib/agent/llm/providers/ollama.ts +151 -0
  47. package/src/lib/agent/llm/providers/openai.ts +153 -0
  48. package/src/lib/agent/llm/sanitizer.ts +170 -0
  49. package/src/lib/agent/llm/types.ts +118 -0
  50. package/src/lib/agent/memory.ts +363 -0
  51. package/src/lib/agent/node-status.ts +56 -0
  52. package/src/lib/agent/node-suggestions.ts +64 -0
  53. package/src/lib/agent/status.ts +80 -0
  54. package/src/lib/agent/suggestions.ts +169 -0
  55. package/src/lib/components/Canvas.svelte +124 -0
  56. package/src/lib/components/ConnectionLine.svelte +46 -0
  57. package/src/lib/components/DisplayNode.svelte +167 -0
  58. package/src/lib/components/InputNode.svelte +158 -0
  59. package/src/lib/components/TerminalNode.svelte +237 -0
  60. package/src/lib/components/Toolbar.svelte +359 -0
  61. package/src/lib/components/TransformNode.svelte +327 -0
  62. package/src/lib/core/index.ts +31 -0
  63. package/src/lib/core/observer.ts +278 -0
  64. package/src/lib/core/redaction.ts +158 -0
  65. package/src/lib/core/shell-adapters/base.ts +325 -0
  66. package/src/lib/core/shell-adapters/bash.ts +110 -0
  67. package/src/lib/core/shell-adapters/index.ts +62 -0
  68. package/src/lib/core/shell-adapters/zsh.ts +105 -0
  69. package/src/lib/core/storage.ts +360 -0
  70. package/src/lib/core/types.ts +176 -0
  71. package/src/lib/design-dojo/Box.svelte +47 -0
  72. package/src/lib/design-dojo/Button.svelte +75 -0
  73. package/src/lib/design-dojo/Input.svelte +65 -0
  74. package/src/lib/design-dojo/List.svelte +38 -0
  75. package/src/lib/design-dojo/Select.svelte +48 -0
  76. package/src/lib/design-dojo/SplitPane.svelte +43 -0
  77. package/src/lib/design-dojo/StatusBar.svelte +61 -0
  78. package/src/lib/design-dojo/Table.svelte +47 -0
  79. package/src/lib/design-dojo/Text.svelte +36 -0
  80. package/src/lib/design-dojo/Toggle.svelte +48 -0
  81. package/src/lib/design-dojo/index.ts +10 -0
  82. package/src/lib/stores/canvas-praxis.ts +268 -0
  83. package/src/lib/stores/canvas.ts +58 -0
  84. package/src/lib/types/agent.ts +78 -0
  85. package/src/lib/types/canvas.ts +71 -0
  86. package/src/lib/utils/storage.ts +326 -0
  87. package/src/lib/utils/yaml-loader.ts +52 -0
  88. package/src/routes/+layout.svelte +5 -0
  89. package/src/routes/+layout.ts +5 -0
  90. package/src/routes/+page.svelte +32 -0
  91. package/src-tauri/Cargo.lock +5735 -0
  92. package/src-tauri/Cargo.toml +38 -0
  93. package/src-tauri/build.rs +3 -0
  94. package/src-tauri/capabilities/default.json +10 -0
  95. package/src-tauri/icons/128x128.png +0 -0
  96. package/src-tauri/icons/128x128@2x.png +0 -0
  97. package/src-tauri/icons/32x32.png +0 -0
  98. package/src-tauri/icons/Square107x107Logo.png +0 -0
  99. package/src-tauri/icons/Square142x142Logo.png +0 -0
  100. package/src-tauri/icons/Square150x150Logo.png +0 -0
  101. package/src-tauri/icons/Square284x284Logo.png +0 -0
  102. package/src-tauri/icons/Square30x30Logo.png +0 -0
  103. package/src-tauri/icons/Square310x310Logo.png +0 -0
  104. package/src-tauri/icons/Square44x44Logo.png +0 -0
  105. package/src-tauri/icons/Square71x71Logo.png +0 -0
  106. package/src-tauri/icons/Square89x89Logo.png +0 -0
  107. package/src-tauri/icons/StoreLogo.png +0 -0
  108. package/src-tauri/icons/icon.icns +0 -0
  109. package/src-tauri/icons/icon.ico +0 -0
  110. package/src-tauri/icons/icon.png +0 -0
  111. package/src-tauri/src/agents/agent1.rs +66 -0
  112. package/src-tauri/src/agents/agent2.rs +80 -0
  113. package/src-tauri/src/agents/agent3.rs +73 -0
  114. package/src-tauri/src/agents/agent4.rs +66 -0
  115. package/src-tauri/src/agents/agent5.rs +68 -0
  116. package/src-tauri/src/agents/agent6.rs +75 -0
  117. package/src-tauri/src/agents/base.rs +52 -0
  118. package/src-tauri/src/agents/mod.rs +17 -0
  119. package/src-tauri/src/core/coordination.rs +117 -0
  120. package/src-tauri/src/core/mod.rs +12 -0
  121. package/src-tauri/src/core/ownership.rs +61 -0
  122. package/src-tauri/src/core/types.rs +132 -0
  123. package/src-tauri/src/execution/mod.rs +5 -0
  124. package/src-tauri/src/execution/runner.rs +143 -0
  125. package/src-tauri/src/lib.rs +161 -0
  126. package/src-tauri/src/main.rs +6 -0
  127. package/src-tauri/src/memory/api.rs +422 -0
  128. package/src-tauri/src/memory/client.rs +156 -0
  129. package/src-tauri/src/memory/encryption.rs +79 -0
  130. package/src-tauri/src/memory/migration.rs +110 -0
  131. package/src-tauri/src/memory/mod.rs +28 -0
  132. package/src-tauri/src/memory/schema.rs +275 -0
  133. package/src-tauri/src/memory/tests.rs +192 -0
  134. package/src-tauri/src/orchestrator/coordinator.rs +232 -0
  135. package/src-tauri/src/orchestrator/mod.rs +13 -0
  136. package/src-tauri/src/orchestrator/planner.rs +304 -0
  137. package/src-tauri/tauri.conf.json +35 -0
  138. package/static/examples/date-time-example.yaml +147 -0
  139. package/static/examples/hello-world.yaml +74 -0
  140. package/static/examples/transform-example.yaml +157 -0
  141. package/static/favicon.png +0 -0
  142. package/static/svelte.svg +1 -0
  143. package/static/tauri.svg +6 -0
  144. package/static/vite.svg +1 -0
  145. package/svelte.config.js +18 -0
  146. package/tsconfig.json +19 -0
  147. package/vite.config.js +45 -0
  148. 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,5 @@
1
+ //! Parallel execution system.
2
+
3
+ pub mod runner;
4
+
5
+ pub use runner::*;
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ // Prevents additional console window on Windows in release, DO NOT REMOVE!!
2
+ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
3
+
4
+ fn main() {
5
+ runebook_lib::run()
6
+ }