@axiom-lattice/local-stores 1.0.2 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/local-stores",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Local SQLite-based stores for Axiom Lattice framework",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,8 +24,8 @@
24
24
  "@langchain/langgraph-checkpoint-sqlite": "^1.0.3",
25
25
  "@types/sql.js": "^1.4.11",
26
26
  "sql.js": "^1.14.1",
27
- "@axiom-lattice/core": "2.1.80",
28
- "@axiom-lattice/protocols": "2.1.42"
27
+ "@axiom-lattice/core": "2.1.81",
28
+ "@axiom-lattice/protocols": "2.1.43"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.11.24",
@@ -34,6 +34,7 @@ import { LocalA2AApiKeyStore } from "./stores/LocalA2AApiKeyStore";
34
34
  import { LocalThreadMessageQueueStore } from "./stores/LocalThreadMessageQueueStore";
35
35
  import { LocalSkillStore } from "./stores/LocalSkillStore";
36
36
  import { LocalScheduleStorage } from "./stores/LocalScheduleStorage";
37
+ import { LocalTaskStore } from "./stores/LocalTaskStore";
37
38
 
38
39
  export interface LocalStoreConfigOptions {
39
40
  /**
@@ -76,6 +77,7 @@ export async function createLocalStoreConfig(options: LocalStoreConfigOptions =
76
77
  threadMessageQueue: new LocalThreadMessageQueueStore(db),
77
78
  skill: new LocalSkillStore(db),
78
79
  schedule: new LocalScheduleStorage(db),
80
+ task: new LocalTaskStore(db),
79
81
  checkpoint: SqliteSaver.fromConnString(dbPath),
80
82
  };
81
83
  }
package/src/index.ts CHANGED
@@ -42,3 +42,4 @@ export { LocalA2AApiKeyStore } from "./stores/LocalA2AApiKeyStore";
42
42
  export { LocalThreadMessageQueueStore } from "./stores/LocalThreadMessageQueueStore";
43
43
  export { LocalSkillStore } from "./stores/LocalSkillStore";
44
44
  export { LocalScheduleStorage } from "./stores/LocalScheduleStorage";
45
+ export { LocalTaskStore } from "./stores/LocalTaskStore";
@@ -17,6 +17,7 @@ CREATE TABLE IF NOT EXISTS lt_assistants (
17
17
  name TEXT NOT NULL,
18
18
  description TEXT,
19
19
  graph_definition TEXT NOT NULL,
20
+ owner_user_id TEXT,
20
21
  created_at TEXT NOT NULL,
21
22
  updated_at TEXT NOT NULL,
22
23
  PRIMARY KEY (tenant_id, id)
@@ -29,6 +30,7 @@ interface AssistantRow {
29
30
  name: string;
30
31
  description: string | null;
31
32
  graph_definition: string;
33
+ owner_user_id: string | null;
32
34
  created_at: string;
33
35
  updated_at: string;
34
36
  }
@@ -63,14 +65,15 @@ export class LocalAssistantStore implements AssistantStore {
63
65
  const now = nowISO();
64
66
 
65
67
  this.db.prepare(
66
- `INSERT INTO lt_assistants (id, tenant_id, name, description, graph_definition, created_at, updated_at)
67
- VALUES (?, ?, ?, ?, ?, ?, ?)
68
+ `INSERT INTO lt_assistants (id, tenant_id, name, description, graph_definition, owner_user_id, created_at, updated_at)
69
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
68
70
  ON CONFLICT(tenant_id, id) DO UPDATE SET
69
71
  name = excluded.name,
70
72
  description = excluded.description,
71
73
  graph_definition = excluded.graph_definition,
74
+ owner_user_id = excluded.owner_user_id,
72
75
  updated_at = excluded.updated_at`,
73
- ).run(id, tenantId, data.name, data.description || null, JSON.stringify(data.graphDefinition), now, now);
76
+ ).run(id, tenantId, data.name, data.description || null, JSON.stringify(data.graphDefinition), data.ownerUserId || null, now, now);
74
77
 
75
78
  return {
76
79
  id,
@@ -78,6 +81,7 @@ export class LocalAssistantStore implements AssistantStore {
78
81
  name: data.name,
79
82
  description: data.description,
80
83
  graphDefinition: data.graphDefinition,
84
+ ownerUserId: data.ownerUserId,
81
85
  createdAt: parseISO(now),
82
86
  updatedAt: parseISO(now),
83
87
  };
@@ -107,6 +111,11 @@ export class LocalAssistantStore implements AssistantStore {
107
111
  values.push(JSON.stringify(updates.graphDefinition));
108
112
  }
109
113
 
114
+ if (updates.ownerUserId !== undefined) {
115
+ setClauses.push("owner_user_id = ?");
116
+ values.push(updates.ownerUserId || null);
117
+ }
118
+
110
119
  if (setClauses.length === 0) return existing;
111
120
 
112
121
  const now = nowISO();
@@ -134,6 +143,13 @@ export class LocalAssistantStore implements AssistantStore {
134
143
  ).get(tenantId, id);
135
144
  return row !== undefined;
136
145
  }
146
+
147
+ async getByOwner(tenantId: string, userId: string): Promise<Assistant | null> {
148
+ const row = this.db.prepare(
149
+ `SELECT * FROM lt_assistants WHERE tenant_id = ? AND owner_user_id = ? LIMIT 1`,
150
+ ).get(tenantId, userId) as unknown as AssistantRow | undefined;
151
+ return row ? mapRowToAssistant(row) : null;
152
+ }
137
153
  }
138
154
 
139
155
  function mapRowToAssistant(row: AssistantRow): Assistant {
@@ -143,6 +159,7 @@ function mapRowToAssistant(row: AssistantRow): Assistant {
143
159
  name: row.name,
144
160
  description: row.description || undefined,
145
161
  graphDefinition: JSON.parse(row.graph_definition),
162
+ ownerUserId: row.owner_user_id || undefined,
146
163
  createdAt: parseISO(row.created_at),
147
164
  updatedAt: parseISO(row.updated_at),
148
165
  };
@@ -73,6 +73,22 @@ export class LocalChannelInstallationStore implements ChannelInstallationStore {
73
73
  return rows.map(mapRow);
74
74
  }
75
75
 
76
+ async getAllInstallations(
77
+ channel?: ChannelInstallationType,
78
+ ): Promise<ChannelInstallation[]> {
79
+ let rows: Row[];
80
+ if (channel) {
81
+ rows = this.db.prepare(
82
+ `SELECT * FROM lt_channel_installations WHERE channel = ? ORDER BY created_at DESC`,
83
+ ).all(channel) as Row[];
84
+ } else {
85
+ rows = this.db.prepare(
86
+ `SELECT * FROM lt_channel_installations ORDER BY created_at DESC`,
87
+ ).all() as Row[];
88
+ }
89
+ return rows.map(mapRow);
90
+ }
91
+
76
92
  async createInstallation(
77
93
  tenantId: string,
78
94
  installationId: string,
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Local SQLite implementation of TaskStore.
3
+ */
4
+
5
+ import { DatabaseWrapper } from "../database";
6
+ import type {
7
+ TaskStore,
8
+ TaskItem,
9
+ CreateTaskRequest,
10
+ UpdateTaskRequest,
11
+ TaskListFilter,
12
+ } from "@axiom-lattice/protocols";
13
+ import { ensureTable, nowISO, parseISO } from "../database";
14
+ import { randomUUID } from "crypto";
15
+
16
+ const DDL = `
17
+ CREATE TABLE IF NOT EXISTS lt_tasks (
18
+ id TEXT NOT NULL,
19
+ tenant_id TEXT NOT NULL,
20
+ owner_type TEXT NOT NULL DEFAULT 'user',
21
+ owner_id TEXT NOT NULL,
22
+ title TEXT NOT NULL,
23
+ description TEXT,
24
+ status TEXT NOT NULL DEFAULT 'pending',
25
+ priority TEXT NOT NULL DEFAULT 'medium',
26
+ due_date TEXT,
27
+ metadata TEXT,
28
+ parent_id TEXT,
29
+ source_id TEXT,
30
+ context TEXT,
31
+ created_at TEXT NOT NULL,
32
+ updated_at TEXT NOT NULL,
33
+ PRIMARY KEY (tenant_id, id)
34
+ );
35
+ `;
36
+
37
+ interface TaskRow {
38
+ id: string;
39
+ tenant_id: string;
40
+ owner_type: string;
41
+ owner_id: string;
42
+ title: string;
43
+ description: string | null;
44
+ status: string;
45
+ priority: string;
46
+ due_date: string | null;
47
+ metadata: string | null;
48
+ parent_id: string | null;
49
+ source_id: string | null;
50
+ context: string | null;
51
+ created_at: string;
52
+ updated_at: string;
53
+ }
54
+
55
+ function mapRowToTask(row: TaskRow): TaskItem {
56
+ return {
57
+ id: row.id,
58
+ tenantId: row.tenant_id,
59
+ ownerType: row.owner_type as 'user' | 'agent',
60
+ ownerId: row.owner_id,
61
+ title: row.title,
62
+ description: row.description ?? undefined,
63
+ status: row.status as TaskItem['status'],
64
+ priority: row.priority as TaskItem['priority'],
65
+ dueDate: row.due_date ?? undefined,
66
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
67
+ parentId: row.parent_id ?? undefined,
68
+ sourceId: row.source_id ?? undefined,
69
+ context: row.context ? JSON.parse(row.context) : undefined,
70
+ createdAt: parseISO(row.created_at),
71
+ updatedAt: parseISO(row.updated_at),
72
+ };
73
+ }
74
+
75
+ export class LocalTaskStore implements TaskStore {
76
+ private db: DatabaseWrapper;
77
+
78
+ constructor(db: DatabaseWrapper) {
79
+ this.db = db;
80
+ ensureTable(db, DDL);
81
+ }
82
+
83
+ async create(
84
+ params: CreateTaskRequest & { tenantId: string; ownerType: string; ownerId: string },
85
+ ): Promise<TaskItem> {
86
+ const id = randomUUID();
87
+ const now = nowISO();
88
+
89
+ this.db.prepare(
90
+ `INSERT INTO lt_tasks (id, tenant_id, owner_type, owner_id, title, description, status, priority, due_date, metadata, parent_id, source_id, context, created_at, updated_at)
91
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
92
+ ).run(
93
+ id, params.tenantId, params.ownerType, params.ownerId, params.title,
94
+ params.description || null, params.status || 'pending', params.priority || 'medium',
95
+ params.dueDate || null, params.metadata ? JSON.stringify(params.metadata) : null,
96
+ params.parentId || null, params.sourceId || null,
97
+ params.context ? JSON.stringify(params.context) : null, now, now,
98
+ );
99
+
100
+ return (await this.getById(params.tenantId, id))!;
101
+ }
102
+
103
+ async getById(tenantId: string, id: string): Promise<TaskItem | null> {
104
+ const row = this.db.prepare(
105
+ `SELECT * FROM lt_tasks WHERE tenant_id = ? AND id = ?`,
106
+ ).get(tenantId, id) as unknown as TaskRow | undefined;
107
+ return row ? mapRowToTask(row) : null;
108
+ }
109
+
110
+ async list(filter: TaskListFilter): Promise<TaskItem[]> {
111
+ const conditions: string[] = ['tenant_id = ?'];
112
+ const params: unknown[] = [filter.tenantId];
113
+
114
+ if (filter.ownerType) { conditions.push('owner_type = ?'); params.push(filter.ownerType); }
115
+ if (filter.ownerId) { conditions.push('owner_id = ?'); params.push(filter.ownerId); }
116
+ if (filter.status) { conditions.push('status = ?'); params.push(filter.status); }
117
+ if (filter.priority) { conditions.push('priority = ?'); params.push(filter.priority); }
118
+ if (filter.parentId) { conditions.push('parent_id = ?'); params.push(filter.parentId); }
119
+ if (filter.sourceId) { conditions.push('source_id = ?'); params.push(filter.sourceId); }
120
+ if (filter.metadata) {
121
+ for (const [key, value] of Object.entries(filter.metadata)) {
122
+ const safeKey = key.replace(/[^a-zA-Z0-9_]/g, '');
123
+ if (safeKey) {
124
+ conditions.push(`json_extract(metadata, '$.${safeKey}') = ?`);
125
+ params.push(String(value));
126
+ }
127
+ }
128
+ }
129
+
130
+ const where = conditions.join(' AND ');
131
+ const limit = filter.limit || 100;
132
+ const offset = filter.offset || 0;
133
+
134
+ const rows = this.db.prepare(
135
+ `SELECT * FROM lt_tasks WHERE ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
136
+ ).all(...params, limit, offset) as unknown as TaskRow[];
137
+ return rows.map(mapRowToTask);
138
+ }
139
+
140
+ async update(
141
+ tenantId: string,
142
+ id: string,
143
+ updates: UpdateTaskRequest,
144
+ ): Promise<TaskItem | null> {
145
+ const existing = await this.getById(tenantId, id);
146
+ if (!existing) return null;
147
+
148
+ const now = nowISO();
149
+ const setClauses: string[] = ['updated_at = ?'];
150
+ const values: unknown[] = [now];
151
+
152
+ if (updates.title !== undefined) { setClauses.push('title = ?'); values.push(updates.title); }
153
+ if (updates.description !== undefined) { setClauses.push('description = ?'); values.push(updates.description); }
154
+ if (updates.status !== undefined) { setClauses.push('status = ?'); values.push(updates.status); }
155
+ if (updates.priority !== undefined) { setClauses.push('priority = ?'); values.push(updates.priority); }
156
+ if (updates.dueDate !== undefined) { setClauses.push('due_date = ?'); values.push(updates.dueDate); }
157
+ if (updates.metadata !== undefined) { setClauses.push('metadata = ?'); values.push(JSON.stringify(updates.metadata)); }
158
+ if (updates.parentId !== undefined) { setClauses.push('parent_id = ?'); values.push(updates.parentId); }
159
+ if (updates.sourceId !== undefined) { setClauses.push('source_id = ?'); values.push(updates.sourceId); }
160
+ if (updates.context !== undefined) { setClauses.push('context = ?'); values.push(JSON.stringify(updates.context)); }
161
+ if (updates.ownerType !== undefined) { setClauses.push('owner_type = ?'); values.push(updates.ownerType); }
162
+ if (updates.ownerId !== undefined) { setClauses.push('owner_id = ?'); values.push(updates.ownerId); }
163
+
164
+ if (setClauses.length === 1) return existing;
165
+
166
+ values.push(tenantId, id);
167
+ this.db.prepare(
168
+ `UPDATE lt_tasks SET ${setClauses.join(', ')} WHERE tenant_id = ? AND id = ?`,
169
+ ).run(...values);
170
+
171
+ return this.getById(tenantId, id);
172
+ }
173
+
174
+ async delete(tenantId: string, id: string): Promise<boolean> {
175
+ const result = this.db.prepare(
176
+ `DELETE FROM lt_tasks WHERE tenant_id = ? AND id = ?`,
177
+ ).run(tenantId, id);
178
+ return result.changes > 0;
179
+ }
180
+ }
@@ -18,6 +18,8 @@ CREATE TABLE IF NOT EXISTS lt_thread_message_queue (
18
18
  thread_id TEXT NOT NULL,
19
19
  tenant_id TEXT NOT NULL,
20
20
  assistant_id TEXT NOT NULL,
21
+ workspace_id TEXT,
22
+ project_id TEXT,
21
23
  message_content TEXT NOT NULL,
22
24
  message_type TEXT NOT NULL DEFAULT 'human',
23
25
  sequence_order INTEGER NOT NULL DEFAULT 0,
@@ -36,6 +38,8 @@ interface QueueRow {
36
38
  thread_id: string;
37
39
  tenant_id: string;
38
40
  assistant_id: string;
41
+ workspace_id: string | null;
42
+ project_id: string | null;
39
43
  message_content: string;
40
44
  message_type: string;
41
45
  sequence_order: number;
@@ -64,10 +68,11 @@ export class LocalThreadMessageQueueStore implements IMessageQueueStore {
64
68
  const nextSeq = seqRow.next_seq;
65
69
 
66
70
  this.db.prepare(
67
- `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
68
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
71
+ `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
72
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
69
73
  ).run(
70
74
  id, params.threadId, params.tenantId, params.assistantId,
75
+ params.workspaceId || null, params.projectId || null,
71
76
  JSON.stringify(params.content), params.type || "human", nextSeq,
72
77
  params.priority || 0,
73
78
  params.command ? JSON.stringify(params.command) : null,
@@ -87,10 +92,11 @@ export class LocalThreadMessageQueueStore implements IMessageQueueStore {
87
92
  ).get(params.threadId) as { next_seq: number };
88
93
 
89
94
  this.db.prepare(
90
- `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
91
- VALUES (?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?)`,
95
+ `INSERT INTO lt_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, created_at)
96
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 100, ?, ?, ?)`,
92
97
  ).run(
93
98
  id, params.threadId, params.tenantId!, params.assistantId!,
99
+ params.workspaceId || null, params.projectId || null,
94
100
  JSON.stringify(params.content), params.type || "human", seqRow.next_seq,
95
101
  params.command ? JSON.stringify(params.command) : null,
96
102
  params.custom_run_config ? JSON.stringify(params.custom_run_config) : null,
@@ -123,12 +129,14 @@ export class LocalThreadMessageQueueStore implements IMessageQueueStore {
123
129
 
124
130
  async getThreadsWithPendingMessages(): Promise<ThreadInfo[]> {
125
131
  const rows = this.db.prepare(
126
- `SELECT DISTINCT tenant_id, assistant_id, thread_id FROM lt_thread_message_queue WHERE status IN ('pending', 'processing') ORDER BY thread_id`,
127
- ).all() as { tenant_id: string; assistant_id: string; thread_id: string }[];
132
+ `SELECT DISTINCT ON (thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id FROM lt_thread_message_queue WHERE status IN ('pending', 'processing') ORDER BY thread_id`,
133
+ ).all() as { tenant_id: string; assistant_id: string; thread_id: string; workspace_id: string | null; project_id: string | null }[];
128
134
  return rows.map((r) => ({
129
135
  tenantId: r.tenant_id,
130
136
  assistantId: r.assistant_id,
131
137
  threadId: r.thread_id,
138
+ workspaceId: r.workspace_id || undefined,
139
+ projectId: r.project_id || undefined,
132
140
  }));
133
141
  }
134
142
 
@@ -42,6 +42,7 @@ CREATE TABLE IF NOT EXISTS lt_workflow_steps (
42
42
  tenant_id TEXT NOT NULL,
43
43
  step_type TEXT NOT NULL,
44
44
  step_name TEXT NOT NULL,
45
+ thread_id TEXT,
45
46
  edge_from TEXT,
46
47
  edge_to TEXT,
47
48
  edge_purpose TEXT,
@@ -57,6 +58,8 @@ CREATE TABLE IF NOT EXISTS lt_workflow_steps (
57
58
  PRIMARY KEY (run_id, id)
58
59
  );
59
60
  CREATE INDEX IF NOT EXISTS idx_lt_ws_type ON lt_workflow_steps(run_id, step_type);
61
+ -- Backfill for existing databases that don't have the column
62
+ ALTER TABLE lt_workflow_steps ADD COLUMN IF NOT EXISTS thread_id TEXT;
60
63
  `;
61
64
 
62
65
  interface RunRow {
@@ -82,6 +85,7 @@ interface StepRow {
82
85
  tenant_id: string;
83
86
  step_type: string;
84
87
  step_name: string;
88
+ thread_id: string | null;
85
89
  edge_from: string | null;
86
90
  edge_to: string | null;
87
91
  edge_purpose: string | null;
@@ -187,15 +191,17 @@ export class LocalWorkflowTrackingStore implements WorkflowTrackingStore {
187
191
  const id = `step_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
188
192
 
189
193
  this.db.prepare(
190
- `INSERT INTO lt_workflow_steps (id, run_id, tenant_id, step_type, step_name, edge_from, edge_to, edge_purpose, input, status, started_at, created_at, updated_at)
191
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?)`,
194
+ `INSERT INTO lt_workflow_steps (id, run_id, tenant_id, step_type, step_name, thread_id, edge_from, edge_to, edge_purpose, input, status, started_at, created_at, updated_at)
195
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?)`,
192
196
  ).run(id, request.runId, request.tenantId, request.stepType, request.stepName,
197
+ request.threadId || null,
193
198
  request.edgeFrom || null, request.edgeTo || null, request.edgePurpose || null,
194
199
  request.input ? JSON.stringify(request.input) : null, now, now, now);
195
200
 
196
201
  return {
197
202
  id, runId: request.runId, tenantId: request.tenantId,
198
203
  stepType: request.stepType, stepName: request.stepName,
204
+ threadId: request.threadId,
199
205
  edgeFrom: request.edgeFrom, edgeTo: request.edgeTo, edgePurpose: request.edgePurpose,
200
206
  input: request.input, status: 'running', startedAt: parseISO(now),
201
207
  createdAt: parseISO(now), updatedAt: parseISO(now),
@@ -207,7 +213,16 @@ export class LocalWorkflowTrackingStore implements WorkflowTrackingStore {
207
213
  const row = this.db.prepare(
208
214
  `SELECT * FROM lt_workflow_steps WHERE run_id = ? AND step_type = ? AND step_name = ?`,
209
215
  ).get(request.runId, request.stepType, request.stepName) as unknown as StepRow | undefined;
210
- if (row) return mapRowToStep(row);
216
+ if (row) {
217
+ const existing = mapRowToStep(row);
218
+ if (!existing.threadId && request.threadId) {
219
+ this.db.prepare(
220
+ `UPDATE lt_workflow_steps SET thread_id = ?, updated_at = ? WHERE run_id = ? AND id = ?`,
221
+ ).run(request.threadId, nowISO(), request.runId, existing.id);
222
+ existing.threadId = request.threadId;
223
+ }
224
+ return existing;
225
+ }
211
226
  return this.createRunStep(request);
212
227
  }
213
228
 
@@ -292,6 +307,7 @@ function mapRowToStep(row: StepRow): RunStep {
292
307
  tenantId: row.tenant_id,
293
308
  stepType: row.step_type as StepType,
294
309
  stepName: row.step_name,
310
+ threadId: row.thread_id,
295
311
  edgeFrom: row.edge_from || undefined,
296
312
  edgeTo: row.edge_to || undefined,
297
313
  edgePurpose: row.edge_purpose || undefined,