@axiom-lattice/gateway 2.1.92 → 2.1.94

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.
@@ -31,6 +31,12 @@ function getTenantId(request: FastifyRequest): string {
31
31
  return (request.headers["x-tenant-id"] as string) || "default";
32
32
  }
33
33
 
34
+ function getUserId(request: FastifyRequest): string | undefined {
35
+ const userId = (request as any).user?.id;
36
+ if (userId) return userId;
37
+ return (request.headers["x-user-id"] as string) || undefined;
38
+ }
39
+
34
40
  /**
35
41
  * Convert AgentConfig to Assistant format
36
42
  */
@@ -112,12 +118,19 @@ export async function getAssistantList(
112
118
 
113
119
  const allAssistants = Array.from(assistantMap.values());
114
120
 
121
+ // Filter: exclude personal assistants owned by other users
122
+ const userId = getUserId(request);
123
+ const filtered = allAssistants.filter((a) => {
124
+ if (!a.ownerUserId) return true;
125
+ return a.ownerUserId === userId;
126
+ });
127
+
115
128
  return {
116
129
  success: true,
117
130
  message: "Successfully retrieved assistant list",
118
131
  data: {
119
- records: allAssistants,
120
- total: allAssistants.length,
132
+ records: filtered,
133
+ total: filtered.length,
121
134
  },
122
135
  };
123
136
  }
@@ -251,6 +251,10 @@ export class AuthController {
251
251
 
252
252
  const token = await this.generateToken(userId, tenantId);
253
253
 
254
+ // Check for personal assistant in this tenant
255
+ const link = await this.userTenantLinkStore.getLink(userId, tenantId);
256
+ const linkMeta = (link?.metadata || {}) as Record<string, unknown>;
257
+
254
258
  return {
255
259
  success: true,
256
260
  data: {
@@ -260,6 +264,11 @@ export class AuthController {
260
264
  status: tenant.status,
261
265
  },
262
266
  token,
267
+ personalAssistant: linkMeta.personalAssistantId ? {
268
+ assistantId: linkMeta.personalAssistantId as string,
269
+ projectId: linkMeta.personalProjectId as string || "default",
270
+ workspaceId: linkMeta.personalWorkspaceId as string || "default",
271
+ } : null,
263
272
  },
264
273
  };
265
274
  } catch (error) {
@@ -0,0 +1,114 @@
1
+ import type { FastifyRequest, FastifyReply } from "fastify";
2
+ import type { CreateMenuItemInput } from "@axiom-lattice/protocols";
3
+ import { getMenuRegistry } from "@axiom-lattice/core";
4
+
5
+ function getTenantId(request: FastifyRequest): string {
6
+ const userTenantId = (request as any).user?.tenantId;
7
+ if (userTenantId) return userTenantId;
8
+ return (request.headers["x-tenant-id"] as string) || "default";
9
+ }
10
+
11
+ function errorMessage(err: unknown): string {
12
+ return err instanceof Error ? err.message : "Unexpected error";
13
+ }
14
+
15
+ export async function getMenuItemList(
16
+ request: FastifyRequest<{
17
+ Querystring: { menuTarget?: string };
18
+ }>,
19
+ _reply: FastifyReply,
20
+ ) {
21
+ const tenantId = getTenantId(request);
22
+ try {
23
+ const registry = getMenuRegistry();
24
+ const items = await registry.list({ tenantId, menuTarget: request.query.menuTarget as "sidebar" | "workspace" | undefined });
25
+ return { success: true, message: "Menu items retrieved", data: { records: items, total: items.length } };
26
+ } catch (error) {
27
+ const msg = errorMessage(error);
28
+ console.error("Failed to get menu items:", msg);
29
+ return { success: false, message: msg, data: { records: [], total: 0 } };
30
+ }
31
+ }
32
+
33
+ export async function getMenuItem(
34
+ request: FastifyRequest<{ Params: { id: string } }>,
35
+ reply: FastifyReply,
36
+ ) {
37
+ const tenantId = getTenantId(request);
38
+ try {
39
+ const registry = getMenuRegistry();
40
+ const item = await registry.getById(request.params.id);
41
+ if (!item || item.tenantId !== tenantId) {
42
+ reply.status(404);
43
+ return { success: false, message: "Menu item not found" };
44
+ }
45
+ return { success: true, message: "Menu item retrieved", data: item };
46
+ } catch (error) {
47
+ const msg = errorMessage(error);
48
+ console.error("Failed to get menu item:", msg);
49
+ reply.status(500);
50
+ return { success: false, message: msg };
51
+ }
52
+ }
53
+
54
+ export async function createMenuItem(
55
+ request: FastifyRequest<{ Body: CreateMenuItemInput }>,
56
+ reply: FastifyReply,
57
+ ) {
58
+ const tenantId = getTenantId(request);
59
+ try {
60
+ const registry = getMenuRegistry();
61
+ const item = await registry.create({ ...request.body, tenantId });
62
+ reply.status(201);
63
+ return { success: true, message: "Menu item created", data: item };
64
+ } catch (error) {
65
+ const msg = errorMessage(error);
66
+ console.error("Failed to create menu item:", msg);
67
+ reply.status(500);
68
+ return { success: false, message: msg };
69
+ }
70
+ }
71
+
72
+ export async function updateMenuItem(
73
+ request: FastifyRequest<{ Params: { id: string }; Body: Partial<CreateMenuItemInput & { enabled: boolean }> }>,
74
+ reply: FastifyReply,
75
+ ) {
76
+ try {
77
+ const tenantId = getTenantId(request);
78
+ const registry = getMenuRegistry();
79
+ const existing = await registry.getById(request.params.id);
80
+ if (!existing || existing.tenantId !== tenantId) {
81
+ reply.status(404);
82
+ return { success: false, message: "Menu item not found" };
83
+ }
84
+ const item = await registry.update(request.params.id, request.body);
85
+ return { success: true, message: "Menu item updated", data: item };
86
+ } catch (error) {
87
+ const msg = errorMessage(error);
88
+ console.error("Failed to update menu item:", msg);
89
+ reply.status(500);
90
+ return { success: false, message: msg };
91
+ }
92
+ }
93
+
94
+ export async function deleteMenuItem(
95
+ request: FastifyRequest<{ Params: { id: string } }>,
96
+ reply: FastifyReply,
97
+ ) {
98
+ try {
99
+ const tenantId = getTenantId(request);
100
+ const registry = getMenuRegistry();
101
+ const existing = await registry.getById(request.params.id);
102
+ if (!existing || existing.tenantId !== tenantId) {
103
+ reply.status(404);
104
+ return { success: false, message: "Menu item not found" };
105
+ }
106
+ await registry.delete(request.params.id);
107
+ return { success: true, message: "Menu item deleted" };
108
+ } catch (error) {
109
+ const msg = errorMessage(error);
110
+ console.error("Failed to delete menu item:", msg);
111
+ reply.status(500);
112
+ return { success: false, message: msg };
113
+ }
114
+ }
@@ -0,0 +1,191 @@
1
+ import { FastifyRequest, FastifyReply } from "fastify";
2
+ import { getStoreLattice, PersonalAssistantConfig } from "@axiom-lattice/core";
3
+ import type { Assistant, CreateAssistantRequest } from "@axiom-lattice/protocols";
4
+ import { randomUUID } from "crypto";
5
+ function getWorkspaceId(request: FastifyRequest): string {
6
+ return (request.headers["x-workspace-id"] as string) || "default";
7
+ }
8
+
9
+ function getTenantId(request: FastifyRequest): string {
10
+ const userTenantId = (request as any).user?.tenantId;
11
+ if (userTenantId) return userTenantId;
12
+ return (request.headers["x-tenant-id"] as string) || "default";
13
+ }
14
+
15
+ function getUserId(request: FastifyRequest): string {
16
+ const userId = (request as any).user?.id
17
+ || (request.headers["x-user-id"] as string);
18
+ if (!userId) throw new Error("User ID is required");
19
+ return userId;
20
+ }
21
+
22
+ interface CreatePersonalAssistantBody {
23
+ name: string;
24
+ personality: string;
25
+ }
26
+
27
+ interface PersonalAssistantStatus {
28
+ id: string;
29
+ name: string;
30
+ projectId: string;
31
+ workspaceId?: string;
32
+ createdAt: Date;
33
+ }
34
+
35
+ interface PersonalAssistantResponse {
36
+ success: boolean;
37
+ message: string;
38
+ data?: PersonalAssistantStatus;
39
+ }
40
+
41
+ function getLinkStore() {
42
+ return getStoreLattice("default", "userTenantLink").store;
43
+ }
44
+
45
+ function getAssistantStore() {
46
+ return getStoreLattice("default", "assistant").store;
47
+ }
48
+
49
+ function getProjectStore() {
50
+ return getStoreLattice("default", "project").store;
51
+ }
52
+
53
+ async function getLinkMeta(userId: string, tenantId: string) {
54
+ const link = await getLinkStore().getLink(userId, tenantId);
55
+ return (link?.metadata || {}) as Record<string, unknown>;
56
+ }
57
+
58
+ async function updateLinkMeta(userId: string, tenantId: string, meta: Record<string, unknown>) {
59
+ await getLinkStore().updateLink(userId, tenantId, { metadata: meta } as any);
60
+ }
61
+
62
+ /**
63
+ * Create a personal assistant for the authenticated user.
64
+ * Random IDs for both assistant and project, stored in UserTenantLink.metadata.
65
+ */
66
+ export async function createPersonalAssistant(
67
+ request: FastifyRequest<{ Body: CreatePersonalAssistantBody }>,
68
+ reply: FastifyReply
69
+ ): Promise<PersonalAssistantResponse> {
70
+ const tenantId = getTenantId(request);
71
+ const userId = getUserId(request);
72
+ const { name, personality } = request.body;
73
+
74
+ if (!name || !personality) {
75
+ return reply.status(400).send({ success: false, message: "name and personality are required" });
76
+ }
77
+
78
+ const store = getAssistantStore();
79
+
80
+ const existing = await store.getByOwner(tenantId, userId);
81
+ if (existing) {
82
+ return reply.status(409).send({ success: false, message: "You already have a personal assistant" });
83
+ }
84
+
85
+ const assistantId = randomUUID();
86
+
87
+ // Create a personal project under the current workspace
88
+ const projectStore = getProjectStore();
89
+ const projectId = randomUUID();
90
+ const wsId = getWorkspaceId(request);
91
+ await projectStore.createProject(tenantId, wsId, projectId, {
92
+ name: `${name}'s Space`,
93
+ description: "Personal workspace for your assistant",
94
+ });
95
+
96
+ const config = PersonalAssistantConfig.get();
97
+ config.key = assistantId;
98
+ PersonalAssistantConfig.render(config, name, personality);
99
+
100
+ const data: CreateAssistantRequest = { name, graphDefinition: config, ownerUserId: userId };
101
+ const assistant = await store.createAssistant(tenantId, assistantId, data);
102
+
103
+ // Store in UserTenantLink.metadata
104
+ const meta = await getLinkMeta(userId, tenantId);
105
+ meta.personalAssistantId = assistantId;
106
+ meta.personalProjectId = projectId;
107
+ meta.personalWorkspaceId = wsId;
108
+ await updateLinkMeta(userId, tenantId, meta);
109
+
110
+ return reply.status(201).send({
111
+ success: true,
112
+ message: "Personal assistant created",
113
+ data: { id: assistant.id, name: assistant.name, projectId, workspaceId: wsId, createdAt: assistant.createdAt },
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Get the authenticated user's personal assistant.
119
+ */
120
+ export async function getPersonalAssistant(
121
+ request: FastifyRequest,
122
+ reply: FastifyReply
123
+ ): Promise<PersonalAssistantResponse> {
124
+ const tenantId = getTenantId(request);
125
+ const userId = getUserId(request);
126
+
127
+ const store = getAssistantStore();
128
+ const assistant = await store.getByOwner(tenantId, userId);
129
+
130
+ if (!assistant) {
131
+ return reply.status(404).send({ success: false, message: "No personal assistant found" });
132
+ }
133
+
134
+ const meta = await getLinkMeta(userId, tenantId);
135
+ const projectId = (meta.personalProjectId as string) || "default";
136
+ const workspaceId = (meta.personalWorkspaceId as string) || "default";
137
+
138
+ return {
139
+ success: true,
140
+ message: "Personal assistant found",
141
+ data: { id: assistant.id, name: assistant.name, projectId, workspaceId, createdAt: assistant.createdAt },
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Delete the authenticated user's personal assistant.
147
+ */
148
+ export async function deletePersonalAssistant(
149
+ request: FastifyRequest,
150
+ reply: FastifyReply
151
+ ): Promise<{ success: boolean; message: string }> {
152
+ const tenantId = getTenantId(request);
153
+ const userId = getUserId(request);
154
+
155
+ const store = getAssistantStore();
156
+ const assistant = await store.getByOwner(tenantId, userId);
157
+
158
+ if (!assistant) {
159
+ return reply.status(404).send({ success: false, message: "No personal assistant found" });
160
+ }
161
+
162
+ const deleted = await store.deleteAssistant(tenantId, assistant.id);
163
+ if (!deleted) {
164
+ return reply.status(500).send({ success: false, message: "Failed to delete personal assistant" });
165
+ }
166
+
167
+ // Delete all threads
168
+ const threadStore = getStoreLattice("default", "thread").store;
169
+ try {
170
+ const threads = await threadStore.getThreadsByAssistantId(tenantId, assistant.id);
171
+ for (const t of threads) {
172
+ await threadStore.deleteThread(tenantId, t.id);
173
+ }
174
+ } catch { /* best effort */ }
175
+
176
+ // Clean up the project
177
+ const meta = await getLinkMeta(userId, tenantId);
178
+ const projectId = meta.personalProjectId as string;
179
+ const wsId = meta.personalWorkspaceId as string;
180
+ if (projectId && wsId) {
181
+ try { await getProjectStore().deleteProject(tenantId, projectId); } catch { /* best effort */ }
182
+ }
183
+
184
+ // Clean up UserTenantLink.metadata
185
+ delete meta.personalAssistantId;
186
+ delete meta.personalProjectId;
187
+ delete meta.personalWorkspaceId;
188
+ await updateLinkMeta(userId, tenantId, meta);
189
+
190
+ return { success: true, message: "Personal assistant deleted" };
191
+ }
@@ -262,3 +262,50 @@ export const abortRun = async (
262
262
  });
263
263
  }
264
264
  };
265
+
266
+ /**
267
+ * Recover agent execution after a crash or manual abort.
268
+ *
269
+ * Resumes the LangGraph from its last checkpoint if the graph was
270
+ * mid-execution (BUSY). Otherwise cleans up stuck queue messages
271
+ * and restarts the queue processor.
272
+ *
273
+ * Pairs with {@link abortRun} — use abort to stop, recover to continue.
274
+ */
275
+ export const recoverRun = async (
276
+ request: FastifyRequest,
277
+ reply: FastifyReply
278
+ ): Promise<void> => {
279
+ try {
280
+ const { assistantId, threadId } = request.params as {
281
+ assistantId: string;
282
+ threadId: string;
283
+ };
284
+ const tenant_id = request.headers["x-tenant-id"] as string;
285
+ const workspace_id = (request.headers["x-workspace-id"] as string) || "default";
286
+ const project_id = (request.headers["x-project-id"] as string) || "default";
287
+
288
+ const agent = agentInstanceManager.getAgent({
289
+ assistant_id: assistantId,
290
+ thread_id: threadId,
291
+ tenant_id,
292
+ workspace_id,
293
+ project_id,
294
+ });
295
+
296
+ await agent.resumeTask();
297
+
298
+ const status = await agent.getRunStatus();
299
+
300
+ reply.status(200).send({
301
+ success: true,
302
+ threadId,
303
+ status,
304
+ });
305
+ } catch (error: any) {
306
+ reply.status(500).send({
307
+ success: false,
308
+ error: `Recover failed: ${error.message}`,
309
+ });
310
+ }
311
+ };
@@ -0,0 +1,187 @@
1
+ import { FastifyRequest, FastifyReply } from "fastify";
2
+ import { getStoreLattice } from "@axiom-lattice/core";
3
+ import type { TaskStore, TaskItem, CreateTaskRequest, UpdateTaskRequest } from "@axiom-lattice/protocols";
4
+
5
+ function getTenantId(request: FastifyRequest): string {
6
+ const userTenantId = (request as any).user?.tenantId;
7
+ if (userTenantId) return userTenantId;
8
+ return (request.headers["x-tenant-id"] as string) || "default";
9
+ }
10
+
11
+ function getUserId(request: FastifyRequest): string {
12
+ const userId = (request as any).user?.id
13
+ || (request.headers["x-user-id"] as string);
14
+ if (!userId) throw new Error("User ID is required");
15
+ return userId;
16
+ }
17
+
18
+ function getStore(): TaskStore {
19
+ return getStoreLattice("default", "task").store as TaskStore;
20
+ }
21
+
22
+ export async function listTasks(
23
+ request: FastifyRequest,
24
+ reply: FastifyReply
25
+ ): Promise<{ success: boolean; data?: TaskItem[]; count?: number; error?: string }> {
26
+ try {
27
+ const tenantId = getTenantId(request);
28
+ const userId = getUserId(request);
29
+ const query = request.query as Record<string, string>;
30
+
31
+ const store = getStore();
32
+ const tasks = await store.list({
33
+ tenantId,
34
+ ownerId: query.ownerId || userId,
35
+ status: query.status,
36
+ priority: query.priority,
37
+ ownerType: query.ownerType as 'user' | 'agent' | undefined,
38
+ metadata: query.metadata ? JSON.parse(query.metadata) : undefined,
39
+ limit: query.limit ? parseInt(query.limit) : undefined,
40
+ offset: query.offset ? parseInt(query.offset) : undefined,
41
+ });
42
+
43
+ return { success: true, data: tasks, count: tasks.length };
44
+ } catch (error: any) {
45
+ return { success: false, error: error.message };
46
+ }
47
+ }
48
+
49
+ export async function getTask(
50
+ request: FastifyRequest<{ Params: { id: string } }>,
51
+ reply: FastifyReply
52
+ ): Promise<{ success: boolean; data?: TaskItem; error?: string }> {
53
+ try {
54
+ const tenantId = getTenantId(request);
55
+ const userId = getUserId(request);
56
+ const { id } = request.params;
57
+
58
+ const store = getStore();
59
+ const task = await store.getById(tenantId, id);
60
+ if (!task) {
61
+ return reply.status(404).send({ success: false, error: "Task not found" });
62
+ }
63
+
64
+ if (task.ownerType === 'user' && task.ownerId !== userId) {
65
+ return reply.status(403).send({ success: false, error: "Access denied" });
66
+ }
67
+
68
+ return { success: true, data: task };
69
+ } catch (error: any) {
70
+ return { success: false, error: error.message };
71
+ }
72
+ }
73
+
74
+ export async function createTask(
75
+ request: FastifyRequest<{ Body: CreateTaskRequest }>,
76
+ reply: FastifyReply
77
+ ): Promise<{ success: boolean; data?: TaskItem; error?: string }> {
78
+ try {
79
+ const tenantId = getTenantId(request);
80
+ const userId = getUserId(request);
81
+ const body = request.body;
82
+
83
+ if (!body.title) {
84
+ return reply.status(400).send({ success: false, error: "title is required" });
85
+ }
86
+
87
+ const store = getStore();
88
+ const task = await store.create({
89
+ ...body,
90
+ tenantId,
91
+ ownerType: (body.ownerType || "user") as 'user' | 'agent',
92
+ ownerId: body.ownerId || userId,
93
+ });
94
+
95
+ return reply.status(201).send({ success: true, data: task });
96
+ } catch (error: any) {
97
+ return reply.status(500).send({ success: false, error: error.message });
98
+ }
99
+ }
100
+
101
+ export async function updateTask(
102
+ request: FastifyRequest<{ Params: { id: string }; Body: UpdateTaskRequest }>,
103
+ reply: FastifyReply
104
+ ): Promise<{ success: boolean; data?: TaskItem; error?: string }> {
105
+ try {
106
+ const tenantId = getTenantId(request);
107
+ const userId = getUserId(request);
108
+ const { id } = request.params;
109
+
110
+ const store = getStore();
111
+ const existing = await store.getById(tenantId, id);
112
+ if (!existing) {
113
+ return reply.status(404).send({ success: false, error: "Task not found" });
114
+ }
115
+
116
+ if (existing.ownerType === 'user' && existing.ownerId !== userId) {
117
+ return reply.status(403).send({ success: false, error: "Access denied" });
118
+ }
119
+
120
+ const task = await store.update(tenantId, id, request.body);
121
+ if (!task) {
122
+ return reply.status(404).send({ success: false, error: "Task not found" });
123
+ }
124
+ return { success: true, data: task };
125
+ } catch (error: any) {
126
+ return reply.status(500).send({ success: false, error: error.message });
127
+ }
128
+ }
129
+
130
+ export async function deleteTask(
131
+ request: FastifyRequest<{ Params: { id: string } }>,
132
+ reply: FastifyReply
133
+ ): Promise<{ success: boolean; message?: string; error?: string }> {
134
+ try {
135
+ const tenantId = getTenantId(request);
136
+ const userId = getUserId(request);
137
+ const { id } = request.params;
138
+
139
+ const store = getStore();
140
+ const existing = await store.getById(tenantId, id);
141
+ if (!existing) {
142
+ return reply.status(404).send({ success: false, error: "Task not found" });
143
+ }
144
+
145
+ if (existing.ownerType === 'user' && existing.ownerId !== userId) {
146
+ return reply.status(403).send({ success: false, error: "Access denied" });
147
+ }
148
+
149
+ const deleted = await store.delete(tenantId, id);
150
+ if (!deleted) {
151
+ return reply.status(404).send({ success: false, error: "Task not found" });
152
+ }
153
+
154
+ return { success: true, message: "Task deleted" };
155
+ } catch (error: any) {
156
+ return reply.status(500).send({ success: false, error: error.message });
157
+ }
158
+ }
159
+
160
+ export async function completeTask(
161
+ request: FastifyRequest<{ Params: { id: string } }>,
162
+ reply: FastifyReply
163
+ ): Promise<{ success: boolean; data?: TaskItem; error?: string }> {
164
+ try {
165
+ const tenantId = getTenantId(request);
166
+ const userId = getUserId(request);
167
+ const { id } = request.params;
168
+
169
+ const store = getStore();
170
+ const existing = await store.getById(tenantId, id);
171
+ if (!existing) {
172
+ return reply.status(404).send({ success: false, error: "Task not found" });
173
+ }
174
+
175
+ if (existing.ownerType === 'user' && existing.ownerId !== userId) {
176
+ return reply.status(403).send({ success: false, error: "Access denied" });
177
+ }
178
+
179
+ const task = await store.update(tenantId, id, { status: "completed" });
180
+ if (!task) {
181
+ return reply.status(404).send({ success: false, error: "Task not found" });
182
+ }
183
+ return { success: true, data: task };
184
+ } catch (error: any) {
185
+ return reply.status(500).send({ success: false, error: error.message });
186
+ }
187
+ }
@@ -262,6 +262,8 @@ export class WorkspaceController {
262
262
  throw new Error("Workspace not found");
263
263
  }
264
264
 
265
+ console.log(`[getBackend] storageType=${workspace.storageType} filePath=${filePath}`);
266
+
265
267
  if (workspace.storageType === "sandbox") {
266
268
  const sandboxManager = getSandBoxManager();
267
269
 
@@ -273,18 +275,23 @@ export class WorkspaceController {
273
275
  projectId,
274
276
  };
275
277
 
278
+ console.log(`[getBackend] trying volume backend for path=${filePath} assistant_id=${assistantId}`);
276
279
  const volumeBackend = await sandboxManager.getVolumeBackendForPath(volumeConfig, filePath || "/project");
277
280
  if (volumeBackend) {
281
+ console.log(`[getBackend] using VolumeFilesystem`);
278
282
  return { backend: volumeBackend, workspace };
279
283
  }
280
284
 
285
+ console.log(`[getBackend] volume not found, falling back to SandboxFilesystem`);
281
286
  const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
287
+ console.log(`[getBackend] sandbox acquired, name=${(sandbox as any).name || "unknown"}`);
282
288
  return {
283
289
  backend: new SandboxFilesystem({
284
290
  sandboxInstance: sandbox,
285
291
  }), workspace
286
292
  };
287
293
  } else {
294
+ console.log(`[getBackend] using FilesystemBackend rootDir=/lattice_store/tenants/${tenantId}/workspaces/${workspaceId}/${projectId}`);
288
295
  return {
289
296
  backend: new FilesystemBackend({
290
297
  rootDir: `/lattice_store/tenants/${tenantId}/workspaces/${workspaceId}/${projectId}`,
@@ -549,8 +556,13 @@ export class WorkspaceController {
549
556
  const path = (request.query.path as string) || "/";
550
557
  const assistantId = request.query.assistantId;
551
558
 
559
+ console.log(`[listPath] tenantId=${tenantId} workspaceId=${workspaceId} projectId=${projectId} path=${path} assistantId=${assistantId}`);
560
+
552
561
  const { backend } = await this.getBackend(tenantId, workspaceId, projectId, assistantId, path);
562
+ console.log(`[listPath] backend type=${backend.constructor.name} calling lsInfo(${path})`);
563
+
553
564
  const files = await backend.lsInfo(path);
565
+ console.log(`[listPath] result count=${files.length}`);
554
566
 
555
567
  return { success: true, data: files };
556
568
  }