@datalayer/core 1.2.25 → 1.2.27
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/lib/api/mcp/tasks.d.ts +9 -2
- package/lib/api/mcp/tasks.js +12 -3
- package/lib/client/constants.d.ts +1 -0
- package/lib/client/constants.js +2 -0
- package/lib/hooks/useCache.d.ts +30 -0
- package/lib/hooks/useCache.js +223 -0
- package/lib/hooks/useMcp.d.ts +2 -2
- package/lib/hooks/useMcp.js +5 -2
- package/lib/state/substates/NavigationState.js +10 -3
- package/package.json +1 -1
package/lib/api/mcp/tasks.d.ts
CHANGED
|
@@ -17,13 +17,20 @@ export declare const listTasks: (token: string, filters?: McpTaskListFilters, ba
|
|
|
17
17
|
export declare const getTask: (token: string, taskUid: string, baseUrl?: string) => Promise<McpTask>;
|
|
18
18
|
/** Stop a task that is still going. Idempotent: a finished task is answered as it is. */
|
|
19
19
|
export declare const cancelTask: (token: string, taskUid: string, baseUrl?: string) => Promise<McpTask>;
|
|
20
|
+
/** What the gateway says of an answer: that it was passed on, not the task. */
|
|
21
|
+
export type McpTaskAnswered = {
|
|
22
|
+
uid: string;
|
|
23
|
+
answered: boolean;
|
|
24
|
+
};
|
|
20
25
|
/**
|
|
21
26
|
* Answer a task that is `input_required`.
|
|
22
27
|
*
|
|
23
28
|
* The input is the tool's own and goes over as typed; only the envelope is
|
|
24
|
-
* converted. The idempotency key makes a retried answer one answer.
|
|
29
|
+
* converted. The idempotency key makes a retried answer one answer. The
|
|
30
|
+
* reply is an acknowledgement, not the task: read the task again for its
|
|
31
|
+
* status.
|
|
25
32
|
*/
|
|
26
|
-
export declare const answerTask: (token: string, taskUid: string, request: McpTaskInput, idempotencyKey: string, baseUrl?: string) => Promise<
|
|
33
|
+
export declare const answerTask: (token: string, taskUid: string, request: McpTaskInput, idempotencyKey: string, baseUrl?: string) => Promise<McpTaskAnswered>;
|
|
27
34
|
/** The tasks of one notebook; empty until milestone 2 fills it. */
|
|
28
35
|
export declare const listNotebookTasks: (token: string, notebookUid: string, filters?: Pick<McpTaskListFilters, "status" | "cursor" | "limit">, baseUrl?: string) => Promise<McpTaskList>;
|
|
29
36
|
/** Where a task's server-sent events are. */
|
package/lib/api/mcp/tasks.js
CHANGED
|
@@ -54,7 +54,9 @@ export const cancelTask = async (token, taskUid, baseUrl = DEFAULT_SERVICE_URLS.
|
|
|
54
54
|
* Answer a task that is `input_required`.
|
|
55
55
|
*
|
|
56
56
|
* The input is the tool's own and goes over as typed; only the envelope is
|
|
57
|
-
* converted. The idempotency key makes a retried answer one answer.
|
|
57
|
+
* converted. The idempotency key makes a retried answer one answer. The
|
|
58
|
+
* reply is an acknowledgement, not the task: read the task again for its
|
|
59
|
+
* status.
|
|
58
60
|
*/
|
|
59
61
|
export const answerTask = async (token, taskUid, request, idempotencyKey, baseUrl = DEFAULT_SERVICE_URLS.JUPYTER_MCP_SERVER) => fromWire(await requestDatalayerAPI({
|
|
60
62
|
url: taskUrl(baseUrl, taskUid, '/input'),
|
|
@@ -90,7 +92,11 @@ export const parseTaskEventFrame = (frame) => {
|
|
|
90
92
|
return undefined;
|
|
91
93
|
}
|
|
92
94
|
const payload = fromWire(JSON.parse(data.join('\n')));
|
|
93
|
-
return {
|
|
95
|
+
return {
|
|
96
|
+
...payload,
|
|
97
|
+
event: payload.event ?? event,
|
|
98
|
+
taskId: payload.taskId ?? '',
|
|
99
|
+
};
|
|
94
100
|
};
|
|
95
101
|
/**
|
|
96
102
|
* Subscribe to a task's events.
|
|
@@ -103,7 +109,10 @@ export const subscribeTaskEvents = (token, taskUid, handlers, baseUrl = DEFAULT_
|
|
|
103
109
|
const controller = new AbortController();
|
|
104
110
|
const run = async () => {
|
|
105
111
|
const response = await fetch(taskEventsUrl(taskUid, baseUrl), {
|
|
106
|
-
headers: {
|
|
112
|
+
headers: {
|
|
113
|
+
Authorization: `Bearer ${token}`,
|
|
114
|
+
Accept: 'text/event-stream',
|
|
115
|
+
},
|
|
107
116
|
signal: controller.signal,
|
|
108
117
|
});
|
|
109
118
|
if (!response.ok || !response.body) {
|
package/lib/client/constants.js
CHANGED
|
@@ -16,6 +16,8 @@ export const ItemTypes = {
|
|
|
16
16
|
NOTEBOOK: 'notebook',
|
|
17
17
|
LEXICAL: 'document',
|
|
18
18
|
DECK: 'deck',
|
|
19
|
+
// A spreadsheet drawn by `@datalayer/cells`: its content is a CellsWorkbook.
|
|
20
|
+
SPREADSHEET: 'spreadsheet',
|
|
19
21
|
EXERCISE: 'exercise',
|
|
20
22
|
CELL: 'cell',
|
|
21
23
|
SPACE: 'space',
|
package/lib/hooks/useCache.d.ts
CHANGED
|
@@ -306,6 +306,12 @@ export declare const queryKeys: {
|
|
|
306
306
|
/** Under the detail: saving the deck refreshes whether it is stale. */
|
|
307
307
|
readonly video: (deckId: string) => readonly ["decks", "detail", string, "video"];
|
|
308
308
|
};
|
|
309
|
+
readonly spreadsheets: {
|
|
310
|
+
readonly all: () => readonly ["spreadsheets"];
|
|
311
|
+
readonly details: () => readonly ["spreadsheets", "detail"];
|
|
312
|
+
readonly detail: (id: string) => readonly ["spreadsheets", "detail", string];
|
|
313
|
+
readonly bySpace: (spaceId: string) => readonly ["spreadsheets", "space", string];
|
|
314
|
+
};
|
|
309
315
|
readonly cells: {
|
|
310
316
|
readonly all: () => readonly ["cells"];
|
|
311
317
|
readonly details: () => readonly ["cells", "detail"];
|
|
@@ -835,6 +841,24 @@ export declare const useCache: ({ loginRoute }?: CacheProps) => {
|
|
|
835
841
|
spec: unknown;
|
|
836
842
|
}, unknown>;
|
|
837
843
|
useDeleteDeckVideo: () => import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
|
|
844
|
+
useSpreadsheet: (spreadsheetId: string) => import("@tanstack/react-query").UseQueryResult<any, Error>;
|
|
845
|
+
useSpreadsheetsBySpace: (spaceId: string) => import("@tanstack/react-query").UseQueryResult<any, Error>;
|
|
846
|
+
useCreateSpreadsheet: () => import("@tanstack/react-query").UseMutationResult<any, Error, {
|
|
847
|
+
spaceId: string;
|
|
848
|
+
name: string;
|
|
849
|
+
description?: string;
|
|
850
|
+
workbook: unknown;
|
|
851
|
+
}, unknown>;
|
|
852
|
+
useUpdateSpreadsheet: () => import("@tanstack/react-query").UseMutationResult<any, Error, {
|
|
853
|
+
id: string;
|
|
854
|
+
name: string;
|
|
855
|
+
description: string;
|
|
856
|
+
}, unknown>;
|
|
857
|
+
useUpdateSpreadsheetModel: () => import("@tanstack/react-query").UseMutationResult<any, Error, {
|
|
858
|
+
id: string;
|
|
859
|
+
workbook: unknown;
|
|
860
|
+
}, unknown>;
|
|
861
|
+
useCloneSpreadsheet: () => import("@tanstack/react-query").UseMutationResult<any, Error, string, unknown>;
|
|
838
862
|
useDocument: (documentId: string) => import("@tanstack/react-query").UseQueryResult<any, Error>;
|
|
839
863
|
useDocumentsBySpace: (spaceId: string) => import("@tanstack/react-query").UseQueryResult<any, Error>;
|
|
840
864
|
useCreateDocument: () => import("@tanstack/react-query").UseMutationResult<any, Error, {
|
|
@@ -1464,6 +1488,12 @@ export declare const useCache: ({ loginRoute }?: CacheProps) => {
|
|
|
1464
1488
|
/** Under the detail: saving the deck refreshes whether it is stale. */
|
|
1465
1489
|
readonly video: (deckId: string) => readonly ["decks", "detail", string, "video"];
|
|
1466
1490
|
};
|
|
1491
|
+
readonly spreadsheets: {
|
|
1492
|
+
readonly all: () => readonly ["spreadsheets"];
|
|
1493
|
+
readonly details: () => readonly ["spreadsheets", "detail"];
|
|
1494
|
+
readonly detail: (id: string) => readonly ["spreadsheets", "detail", string];
|
|
1495
|
+
readonly bySpace: (spaceId: string) => readonly ["spreadsheets", "space", string];
|
|
1496
|
+
};
|
|
1467
1497
|
readonly cells: {
|
|
1468
1498
|
readonly all: () => readonly ["cells"];
|
|
1469
1499
|
readonly details: () => readonly ["cells", "detail"];
|
package/lib/hooks/useCache.js
CHANGED
|
@@ -372,6 +372,13 @@ export const queryKeys = {
|
|
|
372
372
|
/** Under the detail: saving the deck refreshes whether it is stale. */
|
|
373
373
|
video: (deckId) => [...queryKeys.decks.detail(deckId), 'video'],
|
|
374
374
|
},
|
|
375
|
+
// Spreadsheets: `@datalayer/cells` workbooks, kept as items of a space.
|
|
376
|
+
spreadsheets: {
|
|
377
|
+
all: () => ['spreadsheets'],
|
|
378
|
+
details: () => [...queryKeys.spreadsheets.all(), 'detail'],
|
|
379
|
+
detail: (id) => [...queryKeys.spreadsheets.details(), id],
|
|
380
|
+
bySpace: (spaceId) => [...queryKeys.spreadsheets.all(), 'space', spaceId],
|
|
381
|
+
},
|
|
375
382
|
// Cells
|
|
376
383
|
cells: {
|
|
377
384
|
all: () => ['cells'],
|
|
@@ -1101,6 +1108,46 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
1101
1108
|
},
|
|
1102
1109
|
};
|
|
1103
1110
|
};
|
|
1111
|
+
/**
|
|
1112
|
+
* A spreadsheet: a space item whose content is a `@datalayer/cells`
|
|
1113
|
+
* workbook (`CellsWorkbook`), written through the documents routes the way
|
|
1114
|
+
* a Studio app's definition is. `workbook` is the parsed JSON when the
|
|
1115
|
+
* answer inlined it (`GET /lexicals/{uid}` does, a listing does not), and
|
|
1116
|
+
* `undefined` otherwise — never a guess, so an editor cannot open on a
|
|
1117
|
+
* listing's row and draw an empty grid it would then save.
|
|
1118
|
+
*/
|
|
1119
|
+
const toSpreadsheet = (raw) => {
|
|
1120
|
+
let workbook = undefined;
|
|
1121
|
+
if (typeof raw.model_s === 'string' && raw.model_s.trim()) {
|
|
1122
|
+
workbook = JSON.parse(raw.model_s);
|
|
1123
|
+
}
|
|
1124
|
+
return {
|
|
1125
|
+
id: raw.uid,
|
|
1126
|
+
type: 'spreadsheet',
|
|
1127
|
+
name: raw.name_t,
|
|
1128
|
+
description: raw.description_t,
|
|
1129
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
1130
|
+
workbook,
|
|
1131
|
+
public: raw.is_public_b ?? false,
|
|
1132
|
+
creationDate: raw.creation_ts_dt
|
|
1133
|
+
? new Date(raw.creation_ts_dt)
|
|
1134
|
+
: undefined,
|
|
1135
|
+
lastUpdateDate: raw.last_update_ts_dt
|
|
1136
|
+
? new Date(raw.last_update_ts_dt)
|
|
1137
|
+
: undefined,
|
|
1138
|
+
lastPublicationDate: raw.published_ts_dt
|
|
1139
|
+
? new Date(raw.published_ts_dt)
|
|
1140
|
+
: undefined,
|
|
1141
|
+
owner: toItemOwner(raw),
|
|
1142
|
+
space: {
|
|
1143
|
+
handle: raw.handle_s,
|
|
1144
|
+
},
|
|
1145
|
+
organization: {
|
|
1146
|
+
id: raw.organization_uid_s,
|
|
1147
|
+
handle: raw.organization_handle_s,
|
|
1148
|
+
},
|
|
1149
|
+
};
|
|
1150
|
+
};
|
|
1104
1151
|
const toDocument = (doc) => {
|
|
1105
1152
|
const owner = toItemOwner(doc);
|
|
1106
1153
|
return {
|
|
@@ -1404,6 +1451,8 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
1404
1451
|
// read no type and no name, and drew itself as a nameless notebook.
|
|
1405
1452
|
case 'deck':
|
|
1406
1453
|
return toDeck(item);
|
|
1454
|
+
case 'spreadsheet':
|
|
1455
|
+
return toSpreadsheet(item);
|
|
1407
1456
|
case 'document':
|
|
1408
1457
|
return toDocument(item);
|
|
1409
1458
|
case 'exercise':
|
|
@@ -2734,6 +2783,167 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2734
2783
|
});
|
|
2735
2784
|
};
|
|
2736
2785
|
// ============================================================================
|
|
2786
|
+
// Spreadsheet Hooks
|
|
2787
|
+
// ============================================================================
|
|
2788
|
+
//
|
|
2789
|
+
// A spreadsheet is a space item of type `spreadsheet` whose content is a
|
|
2790
|
+
// `@datalayer/cells` workbook. It has no routes of its own: it is created,
|
|
2791
|
+
// read, renamed, saved and cloned through the documents routes
|
|
2792
|
+
// (`/lexicals`, `documentType=spreadsheet`), as a Studio app is, and Spacer
|
|
2793
|
+
// refuses a body that is not a workbook.
|
|
2794
|
+
/**
|
|
2795
|
+
* Get a spreadsheet by id, its workbook inlined as `workbook`.
|
|
2796
|
+
*/
|
|
2797
|
+
const useSpreadsheet = (spreadsheetId) => {
|
|
2798
|
+
return useQuery({
|
|
2799
|
+
queryKey: queryKeys.spreadsheets.detail(spreadsheetId),
|
|
2800
|
+
queryFn: async () => {
|
|
2801
|
+
const resp = await requestDatalayer({
|
|
2802
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/lexicals/${spreadsheetId}`,
|
|
2803
|
+
method: 'GET',
|
|
2804
|
+
});
|
|
2805
|
+
if (resp.success && resp.document) {
|
|
2806
|
+
if (resp.document.type_s !== 'spreadsheet') {
|
|
2807
|
+
throw new Error('This item is not a spreadsheet.');
|
|
2808
|
+
}
|
|
2809
|
+
return toSpreadsheet(resp.document);
|
|
2810
|
+
}
|
|
2811
|
+
if (resp?.code === 'FORBIDDEN') {
|
|
2812
|
+
throw new Error(resp.message || 'Not authorized to access this spreadsheet');
|
|
2813
|
+
}
|
|
2814
|
+
throw new Error(resp.message || 'Failed to fetch spreadsheet');
|
|
2815
|
+
},
|
|
2816
|
+
...DEFAULT_QUERY_OPTIONS,
|
|
2817
|
+
enabled: !!spreadsheetId,
|
|
2818
|
+
});
|
|
2819
|
+
};
|
|
2820
|
+
/**
|
|
2821
|
+
* Get the spreadsheets of a space. A row carries no workbook: the detail
|
|
2822
|
+
* cache is not seeded from it.
|
|
2823
|
+
*/
|
|
2824
|
+
const useSpreadsheetsBySpace = (spaceId) => {
|
|
2825
|
+
return useQuery({
|
|
2826
|
+
queryKey: queryKeys.spreadsheets.bySpace(spaceId),
|
|
2827
|
+
queryFn: async () => {
|
|
2828
|
+
const resp = await requestDatalayer({
|
|
2829
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/spaces/${spaceId}/items/types/spreadsheet`,
|
|
2830
|
+
method: 'GET',
|
|
2831
|
+
});
|
|
2832
|
+
if (resp.success && resp.items) {
|
|
2833
|
+
return resp.items.map((raw) => toSpreadsheet(raw));
|
|
2834
|
+
}
|
|
2835
|
+
return [];
|
|
2836
|
+
},
|
|
2837
|
+
...LIST_QUERY_OPTIONS,
|
|
2838
|
+
enabled: !!spaceId,
|
|
2839
|
+
});
|
|
2840
|
+
};
|
|
2841
|
+
/**
|
|
2842
|
+
* Create a spreadsheet from a workbook: the starter one, or one the
|
|
2843
|
+
* person brings. Sent as the uploaded file, which Spacer checks.
|
|
2844
|
+
*/
|
|
2845
|
+
const useCreateSpreadsheet = () => {
|
|
2846
|
+
return useMutation({
|
|
2847
|
+
mutationFn: async ({ spaceId, name, description, workbook, }) => {
|
|
2848
|
+
const formData = new FormData();
|
|
2849
|
+
formData.append('spaceId', spaceId);
|
|
2850
|
+
formData.append('documentType', 'spreadsheet');
|
|
2851
|
+
formData.append('name', name);
|
|
2852
|
+
formData.append('description', description || '');
|
|
2853
|
+
formData.append('file', new File([JSON.stringify(workbook)], `${name}.cells.json`, {
|
|
2854
|
+
type: 'application/json',
|
|
2855
|
+
}));
|
|
2856
|
+
return uploadDocument(formData);
|
|
2857
|
+
},
|
|
2858
|
+
onSuccess: resp => {
|
|
2859
|
+
if (resp.success && resp.document) {
|
|
2860
|
+
// The answer carries no model: the editor reads the spreadsheet
|
|
2861
|
+
// from the server rather than from a seeded, empty detail.
|
|
2862
|
+
queryClient.invalidateQueries({
|
|
2863
|
+
queryKey: queryKeys.spreadsheets.detail(resp.document.uid),
|
|
2864
|
+
});
|
|
2865
|
+
queryClient.refetchQueries({
|
|
2866
|
+
queryKey: queryKeys.spreadsheets.all(),
|
|
2867
|
+
});
|
|
2868
|
+
queryClient.refetchQueries({ queryKey: queryKeys.items.all() });
|
|
2869
|
+
}
|
|
2870
|
+
},
|
|
2871
|
+
});
|
|
2872
|
+
};
|
|
2873
|
+
/**
|
|
2874
|
+
* Update a spreadsheet's name and description.
|
|
2875
|
+
*/
|
|
2876
|
+
const useUpdateSpreadsheet = () => {
|
|
2877
|
+
return useMutation({
|
|
2878
|
+
mutationFn: async ({ id, name, description, }) => {
|
|
2879
|
+
return requestDatalayer({
|
|
2880
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/lexicals/${id}`,
|
|
2881
|
+
method: 'PUT',
|
|
2882
|
+
body: { name, description },
|
|
2883
|
+
});
|
|
2884
|
+
},
|
|
2885
|
+
onSuccess: (resp, spreadsheet) => {
|
|
2886
|
+
if (resp.success) {
|
|
2887
|
+
queryClient.invalidateQueries({
|
|
2888
|
+
queryKey: queryKeys.spreadsheets.detail(spreadsheet.id),
|
|
2889
|
+
});
|
|
2890
|
+
queryClient.invalidateQueries({
|
|
2891
|
+
queryKey: queryKeys.spreadsheets.all(),
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2894
|
+
},
|
|
2895
|
+
});
|
|
2896
|
+
};
|
|
2897
|
+
/**
|
|
2898
|
+
* Replace a spreadsheet's workbook, whole. A refusal (a workbook Spacer
|
|
2899
|
+
* does not accept) is thrown, not answered as a success.
|
|
2900
|
+
*/
|
|
2901
|
+
const useUpdateSpreadsheetModel = () => {
|
|
2902
|
+
return useMutation({
|
|
2903
|
+
mutationFn: async ({ id, workbook, }) => {
|
|
2904
|
+
const resp = await requestDatalayer({
|
|
2905
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/lexicals/${id}/model`,
|
|
2906
|
+
method: 'PUT',
|
|
2907
|
+
body: { model: workbook },
|
|
2908
|
+
});
|
|
2909
|
+
if (!resp.success) {
|
|
2910
|
+
throw new Error(resp.message || 'The spreadsheet was not saved.');
|
|
2911
|
+
}
|
|
2912
|
+
return resp;
|
|
2913
|
+
},
|
|
2914
|
+
onSuccess: (_, variables) => {
|
|
2915
|
+
queryClient.invalidateQueries({
|
|
2916
|
+
queryKey: queryKeys.spreadsheets.detail(variables.id),
|
|
2917
|
+
});
|
|
2918
|
+
queryClient.invalidateQueries({
|
|
2919
|
+
queryKey: queryKeys.spreadsheets.all(),
|
|
2920
|
+
});
|
|
2921
|
+
},
|
|
2922
|
+
});
|
|
2923
|
+
};
|
|
2924
|
+
/**
|
|
2925
|
+
* Clone a spreadsheet into the caller's library: the reuse the library
|
|
2926
|
+
* counts for a published one.
|
|
2927
|
+
*/
|
|
2928
|
+
const useCloneSpreadsheet = () => {
|
|
2929
|
+
return useMutation({
|
|
2930
|
+
mutationFn: async (spreadsheetId) => {
|
|
2931
|
+
return requestDatalayer({
|
|
2932
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/lexicals/${spreadsheetId}/clone`,
|
|
2933
|
+
method: 'POST',
|
|
2934
|
+
});
|
|
2935
|
+
},
|
|
2936
|
+
onSuccess: resp => {
|
|
2937
|
+
if (resp.success && resp.document) {
|
|
2938
|
+
queryClient.refetchQueries({
|
|
2939
|
+
queryKey: queryKeys.spreadsheets.all(),
|
|
2940
|
+
});
|
|
2941
|
+
queryClient.refetchQueries({ queryKey: queryKeys.items.all() });
|
|
2942
|
+
}
|
|
2943
|
+
},
|
|
2944
|
+
});
|
|
2945
|
+
};
|
|
2946
|
+
// ============================================================================
|
|
2737
2947
|
// Document Hooks
|
|
2738
2948
|
// ============================================================================
|
|
2739
2949
|
/**
|
|
@@ -3196,6 +3406,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
3196
3406
|
queryKey: queryKeys.documents.detail(itemId),
|
|
3197
3407
|
});
|
|
3198
3408
|
queryClient.removeQueries({ queryKey: queryKeys.decks.detail(itemId) });
|
|
3409
|
+
queryClient.removeQueries({
|
|
3410
|
+
queryKey: queryKeys.spreadsheets.detail(itemId),
|
|
3411
|
+
});
|
|
3199
3412
|
queryClient.removeQueries({ queryKey: queryKeys.cells.detail(itemId) });
|
|
3200
3413
|
queryClient.removeQueries({
|
|
3201
3414
|
queryKey: queryKeys.lessons.detail(itemId),
|
|
@@ -3210,6 +3423,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
3210
3423
|
queryClient.invalidateQueries({ queryKey: queryKeys.notebooks.all() });
|
|
3211
3424
|
queryClient.invalidateQueries({ queryKey: queryKeys.documents.all() });
|
|
3212
3425
|
queryClient.invalidateQueries({ queryKey: queryKeys.decks.all() });
|
|
3426
|
+
queryClient.invalidateQueries({
|
|
3427
|
+
queryKey: queryKeys.spreadsheets.all(),
|
|
3428
|
+
});
|
|
3213
3429
|
queryClient.invalidateQueries({ queryKey: queryKeys.cells.all() });
|
|
3214
3430
|
queryClient.invalidateQueries({ queryKey: queryKeys.lessons.all() });
|
|
3215
3431
|
queryClient.invalidateQueries({ queryKey: queryKeys.exercises.all() });
|
|
@@ -8692,6 +8908,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
8692
8908
|
useSavedDeckVideo,
|
|
8693
8909
|
useSaveDeckVideo,
|
|
8694
8910
|
useDeleteDeckVideo,
|
|
8911
|
+
// Spreadsheets
|
|
8912
|
+
useSpreadsheet,
|
|
8913
|
+
useSpreadsheetsBySpace,
|
|
8914
|
+
useCreateSpreadsheet,
|
|
8915
|
+
useUpdateSpreadsheet,
|
|
8916
|
+
useUpdateSpreadsheetModel,
|
|
8917
|
+
useCloneSpreadsheet,
|
|
8695
8918
|
// Documents
|
|
8696
8919
|
useDocument,
|
|
8697
8920
|
useDocumentsBySpace,
|
package/lib/hooks/useMcp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type McpActivity, type McpActivityFilters, type McpAuditFilters, type McpBindingListFilters, type McpGatewayVersion, type McpMetricsFilters, type McpMetricsSnapshot, type McpOrganizationOverview, type McpOrganizationUsage, type McpOrganizationOverviewFilters, type McpPolicyFilters, type McpRunLogs, type McpRunTrace, type McpTaskEvent, type McpTaskListFilters, type McpWorkerList, type McpWorkflowsHealth } from '../api/mcp';
|
|
1
|
+
import { type McpTaskAnswered, type McpActivity, type McpActivityFilters, type McpAuditFilters, type McpBindingListFilters, type McpGatewayVersion, type McpMetricsFilters, type McpMetricsSnapshot, type McpOrganizationOverview, type McpOrganizationUsage, type McpOrganizationOverviewFilters, type McpPolicyFilters, type McpRunLogs, type McpRunTrace, type McpTaskEvent, type McpTaskListFilters, type McpWorkerList, type McpWorkflowsHealth } from '../api/mcp';
|
|
2
2
|
import { type ConnectedAgent } from '../api/iam/connectedAgents';
|
|
3
3
|
import { type OrganizationTeam } from '../api/iam/organizationTeams';
|
|
4
4
|
import { setAuditSettings, type McpAuditSettings } from '../api/iam/mcpAuditSettings';
|
|
@@ -29,7 +29,7 @@ export declare const useTaskEvents: (taskUid?: string, options?: {
|
|
|
29
29
|
onEvent?: (event: McpTaskEvent) => void;
|
|
30
30
|
}) => void;
|
|
31
31
|
export declare const useCancelTask: () => import("@tanstack/react-query").UseMutationResult<McpTask, Error, string, unknown>;
|
|
32
|
-
export declare const useAnswerTask: () => import("@tanstack/react-query").UseMutationResult<
|
|
32
|
+
export declare const useAnswerTask: () => import("@tanstack/react-query").UseMutationResult<McpTaskAnswered, Error, {
|
|
33
33
|
taskUid: string;
|
|
34
34
|
input: Record<string, unknown>;
|
|
35
35
|
}, unknown>;
|
package/lib/hooks/useMcp.js
CHANGED
|
@@ -137,8 +137,11 @@ export const useAnswerTask = () => {
|
|
|
137
137
|
const mcpUrl = useMcpServerUrl();
|
|
138
138
|
return useMutation({
|
|
139
139
|
mutationFn: ({ taskUid, input }) => answerTask(token ?? '', taskUid, { input }, crypto.randomUUID(), mcpUrl),
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
// The gateway acknowledges the answer, it does not send the task back:
|
|
141
|
+
// read the task again rather than writing the acknowledgement in its
|
|
142
|
+
// place (the run page read its status from it and fell).
|
|
143
|
+
onSuccess: (_answered, { taskUid }) => {
|
|
144
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.mcp.task(taskUid) });
|
|
142
145
|
queryClient.invalidateQueries({ queryKey: queryKeys.mcp.tasks() });
|
|
143
146
|
},
|
|
144
147
|
});
|
|
@@ -294,7 +294,13 @@ export function viewForRoute(route, current) {
|
|
|
294
294
|
if (path === '/research' || path.startsWith('/research/')) {
|
|
295
295
|
return 'research';
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
// The SVG gallery is the admin sidebar's, at its own address: filed under
|
|
298
|
+
// the Platform, the Platform button came back to it with the admin sidebar
|
|
299
|
+
// beside it (reported 28 October 2026).
|
|
300
|
+
if (path === '/admin' ||
|
|
301
|
+
path.startsWith('/admin/') ||
|
|
302
|
+
path === '/svg' ||
|
|
303
|
+
path.startsWith('/svg/')) {
|
|
298
304
|
return 'admin';
|
|
299
305
|
}
|
|
300
306
|
// The Apps shell, and the hosted applications at `/apps/<slug>` with it.
|
|
@@ -311,10 +317,11 @@ export function viewForRoute(route, current) {
|
|
|
311
317
|
}
|
|
312
318
|
/**
|
|
313
319
|
* `/<account>/<space>/<kind>[/…]` for the kinds a space shows beside the
|
|
314
|
-
* Agentify sidebar — decks, courses and what a course
|
|
320
|
+
* Agentify sidebar — decks, spreadsheets (Cells), courses and what a course
|
|
321
|
+
* is made of. The
|
|
315
322
|
* documentation (`/docs/…`) is never an account.
|
|
316
323
|
*/
|
|
317
|
-
const AGENTIFY_SPACE_PAGE = /^\/(?!docs\/)[^/]+\/[^/]+\/(?:decks|courses|course|lessons|exercises|assignments)(?:\/|$)/;
|
|
324
|
+
const AGENTIFY_SPACE_PAGE = /^\/(?!docs\/)[^/]+\/[^/]+\/(?:decks|spreadsheets|courses|course|lessons|exercises|assignments)(?:\/|$)/;
|
|
318
325
|
/** `/<account>/courses`: an account's courses, beside the Agentify sidebar. */
|
|
319
326
|
const AGENTIFY_ACCOUNT_PAGE = /^\/(?!docs\/)[^/]+\/courses$/;
|
|
320
327
|
/** Whether a page may be the one a shell returns to. */
|