@sesamespace/hivemind 0.1.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/README.md +86 -0
- package/config/TEAM-CHARTER.md +87 -0
- package/config/default.toml +39 -0
- package/dist/__tests__/fleet-integration.test.d.ts +9 -0
- package/dist/__tests__/fleet-integration.test.d.ts.map +1 -0
- package/dist/__tests__/fleet-integration.test.js +201 -0
- package/dist/__tests__/fleet-integration.test.js.map +1 -0
- package/dist/__tests__/fleet.test.d.ts +7 -0
- package/dist/__tests__/fleet.test.d.ts.map +1 -0
- package/dist/__tests__/fleet.test.js +171 -0
- package/dist/__tests__/fleet.test.js.map +1 -0
- package/dist/__tests__/integration.test.d.ts +2 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +348 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/agent.d.ts +27 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +217 -0
- package/dist/agent.js.map +1 -0
- package/dist/commands/fleet.d.ts +13 -0
- package/dist/commands/fleet.d.ts.map +1 -0
- package/dist/commands/fleet.js +193 -0
- package/dist/commands/fleet.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +170 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +39 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +50 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +153 -0
- package/dist/context.js.map +1 -0
- package/dist/fleet/fleet-manager.d.ts +86 -0
- package/dist/fleet/fleet-manager.d.ts.map +1 -0
- package/dist/fleet/fleet-manager.js +298 -0
- package/dist/fleet/fleet-manager.js.map +1 -0
- package/dist/fleet/memory-sync.d.ts +91 -0
- package/dist/fleet/memory-sync.d.ts.map +1 -0
- package/dist/fleet/memory-sync.js +292 -0
- package/dist/fleet/memory-sync.js.map +1 -0
- package/dist/fleet/primary-client.d.ts +49 -0
- package/dist/fleet/primary-client.d.ts.map +1 -0
- package/dist/fleet/primary-client.js +222 -0
- package/dist/fleet/primary-client.js.map +1 -0
- package/dist/fleet/worker-protocol.d.ts +125 -0
- package/dist/fleet/worker-protocol.d.ts.map +1 -0
- package/dist/fleet/worker-protocol.js +27 -0
- package/dist/fleet/worker-protocol.js.map +1 -0
- package/dist/fleet/worker-server.d.ts +53 -0
- package/dist/fleet/worker-server.d.ts.map +1 -0
- package/dist/fleet/worker-server.js +191 -0
- package/dist/fleet/worker-server.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/llm-client.d.ts +24 -0
- package/dist/llm-client.d.ts.map +1 -0
- package/dist/llm-client.js +40 -0
- package/dist/llm-client.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +41 -0
- package/dist/main.js.map +1 -0
- package/dist/memory-client.d.ts +88 -0
- package/dist/memory-client.d.ts.map +1 -0
- package/dist/memory-client.js +185 -0
- package/dist/memory-client.js.map +1 -0
- package/dist/pipeline.d.ts +2 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +125 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +75 -0
- package/dist/prompt.js.map +1 -0
- package/dist/sesame.d.ts +33 -0
- package/dist/sesame.d.ts.map +1 -0
- package/dist/sesame.js +67 -0
- package/dist/sesame.js.map +1 -0
- package/dist/start.d.ts +3 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +20 -0
- package/dist/start.js.map +1 -0
- package/dist/task-engine.d.ts +32 -0
- package/dist/task-engine.d.ts.map +1 -0
- package/dist/task-engine.js +80 -0
- package/dist/task-engine.js.map +1 -0
- package/dist/worker.d.ts +73 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +279 -0
- package/dist/worker.js.map +1 -0
- package/install.sh +186 -0
- package/package.json +36 -0
- package/packages/memory/Cargo.lock +6480 -0
- package/packages/memory/Cargo.toml +21 -0
- package/packages/memory/src/src/context.rs +179 -0
- package/packages/memory/src/src/embeddings.rs +51 -0
- package/packages/memory/src/src/main.rs +626 -0
- package/packages/memory/src/src/promotion.rs +637 -0
- package/packages/memory/src/src/scoring.rs +131 -0
- package/packages/memory/src/src/store.rs +460 -0
- package/packages/memory/src/src/tasks.rs +321 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use arrow_array::{RecordBatch, RecordBatchIterator, StringArray};
|
|
3
|
+
use arrow_schema::{DataType, Field, Schema};
|
|
4
|
+
use chrono::Utc;
|
|
5
|
+
use futures::stream::TryStreamExt;
|
|
6
|
+
use lancedb::{connection::Connection, query::ExecutableQuery, query::QueryBase, Table};
|
|
7
|
+
use serde::{Deserialize, Serialize};
|
|
8
|
+
use std::sync::Arc;
|
|
9
|
+
|
|
10
|
+
const TASKS_TABLE: &str = "tasks";
|
|
11
|
+
|
|
12
|
+
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
13
|
+
pub struct TaskRecord {
|
|
14
|
+
pub id: String,
|
|
15
|
+
pub context_name: String,
|
|
16
|
+
pub title: String,
|
|
17
|
+
pub description: String,
|
|
18
|
+
pub status: String,
|
|
19
|
+
pub blocked_by: String, // JSON array stored as string
|
|
20
|
+
pub created_at: String,
|
|
21
|
+
pub updated_at: String,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[derive(Debug, Deserialize)]
|
|
25
|
+
pub struct TaskInput {
|
|
26
|
+
pub context_name: String,
|
|
27
|
+
pub title: String,
|
|
28
|
+
pub description: String,
|
|
29
|
+
#[serde(default = "default_status")]
|
|
30
|
+
pub status: String,
|
|
31
|
+
#[serde(default)]
|
|
32
|
+
pub blocked_by: Vec<String>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn default_status() -> String {
|
|
36
|
+
"planned".to_string()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[derive(Debug, Deserialize)]
|
|
40
|
+
pub struct TaskUpdate {
|
|
41
|
+
pub status: Option<String>,
|
|
42
|
+
pub title: Option<String>,
|
|
43
|
+
pub description: Option<String>,
|
|
44
|
+
pub blocked_by: Option<Vec<String>>,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub struct TaskStore {
|
|
48
|
+
db: Connection,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
impl TaskStore {
|
|
52
|
+
pub async fn new(db: Connection) -> Result<Self> {
|
|
53
|
+
let store = Self { db };
|
|
54
|
+
store.ensure_table().await?;
|
|
55
|
+
Ok(store)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn schema() -> Arc<Schema> {
|
|
59
|
+
Arc::new(Schema::new(vec![
|
|
60
|
+
Field::new("id", DataType::Utf8, false),
|
|
61
|
+
Field::new("context_name", DataType::Utf8, false),
|
|
62
|
+
Field::new("title", DataType::Utf8, false),
|
|
63
|
+
Field::new("description", DataType::Utf8, false),
|
|
64
|
+
Field::new("status", DataType::Utf8, false),
|
|
65
|
+
Field::new("blocked_by", DataType::Utf8, false),
|
|
66
|
+
Field::new("created_at", DataType::Utf8, false),
|
|
67
|
+
Field::new("updated_at", DataType::Utf8, false),
|
|
68
|
+
]))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async fn ensure_table(&self) -> Result<()> {
|
|
72
|
+
let names = self.db.table_names().execute().await?;
|
|
73
|
+
if !names.contains(&TASKS_TABLE.to_string()) {
|
|
74
|
+
let schema = Self::schema();
|
|
75
|
+
let batch = RecordBatch::new_empty(schema.clone());
|
|
76
|
+
let batches = RecordBatchIterator::new(vec![Ok(batch)], schema);
|
|
77
|
+
self.db
|
|
78
|
+
.create_table(TASKS_TABLE, Box::new(batches))
|
|
79
|
+
.execute()
|
|
80
|
+
.await?;
|
|
81
|
+
tracing::info!("Created tasks table");
|
|
82
|
+
}
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pub async fn create_task(&self, input: TaskInput) -> Result<TaskRecord> {
|
|
87
|
+
let id = uuid::Uuid::new_v4().to_string();
|
|
88
|
+
let now = Utc::now().to_rfc3339();
|
|
89
|
+
let blocked_by_json = serde_json::to_string(&input.blocked_by)?;
|
|
90
|
+
|
|
91
|
+
let task = TaskRecord {
|
|
92
|
+
id: id.clone(),
|
|
93
|
+
context_name: input.context_name.clone(),
|
|
94
|
+
title: input.title.clone(),
|
|
95
|
+
description: input.description.clone(),
|
|
96
|
+
status: input.status,
|
|
97
|
+
blocked_by: blocked_by_json.clone(),
|
|
98
|
+
created_at: now.clone(),
|
|
99
|
+
updated_at: now.clone(),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
let schema = Self::schema();
|
|
103
|
+
let batch = RecordBatch::try_new(
|
|
104
|
+
schema.clone(),
|
|
105
|
+
vec![
|
|
106
|
+
Arc::new(StringArray::from(vec![task.id.as_str()])),
|
|
107
|
+
Arc::new(StringArray::from(vec![task.context_name.as_str()])),
|
|
108
|
+
Arc::new(StringArray::from(vec![task.title.as_str()])),
|
|
109
|
+
Arc::new(StringArray::from(vec![task.description.as_str()])),
|
|
110
|
+
Arc::new(StringArray::from(vec![task.status.as_str()])),
|
|
111
|
+
Arc::new(StringArray::from(vec![blocked_by_json.as_str()])),
|
|
112
|
+
Arc::new(StringArray::from(vec![task.created_at.as_str()])),
|
|
113
|
+
Arc::new(StringArray::from(vec![task.updated_at.as_str()])),
|
|
114
|
+
],
|
|
115
|
+
)?;
|
|
116
|
+
|
|
117
|
+
let table = self.db.open_table(TASKS_TABLE).execute().await?;
|
|
118
|
+
let batches = RecordBatchIterator::new(vec![Ok(batch)], schema);
|
|
119
|
+
table.add(Box::new(batches)).execute().await?;
|
|
120
|
+
|
|
121
|
+
tracing::debug!("Created task {} in context {}", task.id, task.context_name);
|
|
122
|
+
Ok(task)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
pub async fn list_tasks(
|
|
126
|
+
&self,
|
|
127
|
+
context: &str,
|
|
128
|
+
status_filter: Option<&str>,
|
|
129
|
+
) -> Result<Vec<TaskRecord>> {
|
|
130
|
+
let table = self.db.open_table(TASKS_TABLE).execute().await?;
|
|
131
|
+
|
|
132
|
+
let filter = match status_filter {
|
|
133
|
+
Some(status) => format!("context_name = '{}' AND status = '{}'", context, status),
|
|
134
|
+
None => format!("context_name = '{}'", context),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
let results = table.query().only_if(filter).execute().await?;
|
|
138
|
+
|
|
139
|
+
let mut tasks = Vec::new();
|
|
140
|
+
let batches: Vec<RecordBatch> = results.try_collect().await?;
|
|
141
|
+
|
|
142
|
+
for batch in &batches {
|
|
143
|
+
let ids = batch
|
|
144
|
+
.column_by_name("id")
|
|
145
|
+
.unwrap()
|
|
146
|
+
.as_any()
|
|
147
|
+
.downcast_ref::<StringArray>()
|
|
148
|
+
.unwrap();
|
|
149
|
+
let ctx_names = batch
|
|
150
|
+
.column_by_name("context_name")
|
|
151
|
+
.unwrap()
|
|
152
|
+
.as_any()
|
|
153
|
+
.downcast_ref::<StringArray>()
|
|
154
|
+
.unwrap();
|
|
155
|
+
let titles = batch
|
|
156
|
+
.column_by_name("title")
|
|
157
|
+
.unwrap()
|
|
158
|
+
.as_any()
|
|
159
|
+
.downcast_ref::<StringArray>()
|
|
160
|
+
.unwrap();
|
|
161
|
+
let descriptions = batch
|
|
162
|
+
.column_by_name("description")
|
|
163
|
+
.unwrap()
|
|
164
|
+
.as_any()
|
|
165
|
+
.downcast_ref::<StringArray>()
|
|
166
|
+
.unwrap();
|
|
167
|
+
let statuses = batch
|
|
168
|
+
.column_by_name("status")
|
|
169
|
+
.unwrap()
|
|
170
|
+
.as_any()
|
|
171
|
+
.downcast_ref::<StringArray>()
|
|
172
|
+
.unwrap();
|
|
173
|
+
let blocked_bys = batch
|
|
174
|
+
.column_by_name("blocked_by")
|
|
175
|
+
.unwrap()
|
|
176
|
+
.as_any()
|
|
177
|
+
.downcast_ref::<StringArray>()
|
|
178
|
+
.unwrap();
|
|
179
|
+
let created_ats = batch
|
|
180
|
+
.column_by_name("created_at")
|
|
181
|
+
.unwrap()
|
|
182
|
+
.as_any()
|
|
183
|
+
.downcast_ref::<StringArray>()
|
|
184
|
+
.unwrap();
|
|
185
|
+
let updated_ats = batch
|
|
186
|
+
.column_by_name("updated_at")
|
|
187
|
+
.unwrap()
|
|
188
|
+
.as_any()
|
|
189
|
+
.downcast_ref::<StringArray>()
|
|
190
|
+
.unwrap();
|
|
191
|
+
|
|
192
|
+
for i in 0..batch.num_rows() {
|
|
193
|
+
tasks.push(TaskRecord {
|
|
194
|
+
id: ids.value(i).to_string(),
|
|
195
|
+
context_name: ctx_names.value(i).to_string(),
|
|
196
|
+
title: titles.value(i).to_string(),
|
|
197
|
+
description: descriptions.value(i).to_string(),
|
|
198
|
+
status: statuses.value(i).to_string(),
|
|
199
|
+
blocked_by: blocked_bys.value(i).to_string(),
|
|
200
|
+
created_at: created_ats.value(i).to_string(),
|
|
201
|
+
updated_at: updated_ats.value(i).to_string(),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Sort by created_at
|
|
207
|
+
tasks.sort_by(|a, b| a.created_at.cmp(&b.created_at));
|
|
208
|
+
|
|
209
|
+
Ok(tasks)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
pub async fn get_task(&self, id: &str) -> Result<Option<TaskRecord>> {
|
|
213
|
+
let table = self.db.open_table(TASKS_TABLE).execute().await?;
|
|
214
|
+
let results = table
|
|
215
|
+
.query()
|
|
216
|
+
.only_if(format!("id = '{}'", id))
|
|
217
|
+
.execute()
|
|
218
|
+
.await?;
|
|
219
|
+
|
|
220
|
+
let batches: Vec<RecordBatch> = results.try_collect().await?;
|
|
221
|
+
for batch in &batches {
|
|
222
|
+
if batch.num_rows() > 0 {
|
|
223
|
+
let ids = batch.column_by_name("id").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
224
|
+
let ctx_names = batch.column_by_name("context_name").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
225
|
+
let titles = batch.column_by_name("title").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
226
|
+
let descriptions = batch.column_by_name("description").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
227
|
+
let statuses = batch.column_by_name("status").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
228
|
+
let blocked_bys = batch.column_by_name("blocked_by").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
229
|
+
let created_ats = batch.column_by_name("created_at").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
230
|
+
let updated_ats = batch.column_by_name("updated_at").unwrap().as_any().downcast_ref::<StringArray>().unwrap();
|
|
231
|
+
|
|
232
|
+
return Ok(Some(TaskRecord {
|
|
233
|
+
id: ids.value(0).to_string(),
|
|
234
|
+
context_name: ctx_names.value(0).to_string(),
|
|
235
|
+
title: titles.value(0).to_string(),
|
|
236
|
+
description: descriptions.value(0).to_string(),
|
|
237
|
+
status: statuses.value(0).to_string(),
|
|
238
|
+
blocked_by: blocked_bys.value(0).to_string(),
|
|
239
|
+
created_at: created_ats.value(0).to_string(),
|
|
240
|
+
updated_at: updated_ats.value(0).to_string(),
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Ok(None)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
pub async fn update_task(&self, id: &str, update: TaskUpdate) -> Result<Option<TaskRecord>> {
|
|
249
|
+
let existing = self.get_task(id).await?;
|
|
250
|
+
let Some(mut task) = existing else {
|
|
251
|
+
return Ok(None);
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Apply updates
|
|
255
|
+
if let Some(status) = update.status {
|
|
256
|
+
task.status = status;
|
|
257
|
+
}
|
|
258
|
+
if let Some(title) = update.title {
|
|
259
|
+
task.title = title;
|
|
260
|
+
}
|
|
261
|
+
if let Some(description) = update.description {
|
|
262
|
+
task.description = description;
|
|
263
|
+
}
|
|
264
|
+
if let Some(blocked_by) = update.blocked_by {
|
|
265
|
+
task.blocked_by = serde_json::to_string(&blocked_by)?;
|
|
266
|
+
}
|
|
267
|
+
task.updated_at = Utc::now().to_rfc3339();
|
|
268
|
+
|
|
269
|
+
// Delete and reinsert
|
|
270
|
+
let table = self.db.open_table(TASKS_TABLE).execute().await?;
|
|
271
|
+
table.delete(&format!("id = '{}'", id)).await?;
|
|
272
|
+
|
|
273
|
+
let schema = Self::schema();
|
|
274
|
+
let batch = RecordBatch::try_new(
|
|
275
|
+
schema.clone(),
|
|
276
|
+
vec![
|
|
277
|
+
Arc::new(StringArray::from(vec![task.id.as_str()])),
|
|
278
|
+
Arc::new(StringArray::from(vec![task.context_name.as_str()])),
|
|
279
|
+
Arc::new(StringArray::from(vec![task.title.as_str()])),
|
|
280
|
+
Arc::new(StringArray::from(vec![task.description.as_str()])),
|
|
281
|
+
Arc::new(StringArray::from(vec![task.status.as_str()])),
|
|
282
|
+
Arc::new(StringArray::from(vec![task.blocked_by.as_str()])),
|
|
283
|
+
Arc::new(StringArray::from(vec![task.created_at.as_str()])),
|
|
284
|
+
Arc::new(StringArray::from(vec![task.updated_at.as_str()])),
|
|
285
|
+
],
|
|
286
|
+
)?;
|
|
287
|
+
|
|
288
|
+
let batches = RecordBatchIterator::new(vec![Ok(batch)], schema);
|
|
289
|
+
table.add(Box::new(batches)).execute().await?;
|
|
290
|
+
|
|
291
|
+
Ok(Some(task))
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// Get the next available task for a context:
|
|
295
|
+
/// - Status is "planned" (not active/complete/archived)
|
|
296
|
+
/// - Not blocked by any incomplete tasks
|
|
297
|
+
pub async fn get_next_task(&self, context: &str) -> Result<Option<TaskRecord>> {
|
|
298
|
+
let planned = self.list_tasks(context, Some("planned")).await?;
|
|
299
|
+
let all_tasks = self.list_tasks(context, None).await?;
|
|
300
|
+
|
|
301
|
+
// Build a set of complete task IDs
|
|
302
|
+
let complete_ids: std::collections::HashSet<String> = all_tasks
|
|
303
|
+
.iter()
|
|
304
|
+
.filter(|t| t.status == "complete" || t.status == "archived")
|
|
305
|
+
.map(|t| t.id.clone())
|
|
306
|
+
.collect();
|
|
307
|
+
|
|
308
|
+
for task in planned {
|
|
309
|
+
let blocked_by: Vec<String> =
|
|
310
|
+
serde_json::from_str(&task.blocked_by).unwrap_or_default();
|
|
311
|
+
|
|
312
|
+
// Task is available if all blockers are complete
|
|
313
|
+
let is_blocked = blocked_by.iter().any(|b| !complete_ids.contains(b));
|
|
314
|
+
if !is_blocked {
|
|
315
|
+
return Ok(Some(task));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
Ok(None)
|
|
320
|
+
}
|
|
321
|
+
}
|