@rockhopper-co/mcp-server 2.1.82-staging.65cfbf4 → 2.1.84-staging.bac34fe
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/CHANGELOG.md +10 -0
- package/dist/account-link-providers.d.ts +42 -0
- package/dist/account-link-providers.d.ts.map +1 -0
- package/dist/account-link-providers.js +47 -0
- package/dist/account-link-providers.js.map +1 -0
- package/dist/api-client.d.ts +16 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +26 -1
- package/dist/api-client.js.map +1 -1
- package/dist/cell-history-unavailable.d.ts +7 -0
- package/dist/cell-history-unavailable.d.ts.map +1 -1
- package/dist/cell-history-unavailable.js +28 -0
- package/dist/cell-history-unavailable.js.map +1 -1
- package/dist/comment-location.d.ts +18 -0
- package/dist/comment-location.d.ts.map +1 -0
- package/dist/comment-location.js +29 -0
- package/dist/comment-location.js.map +1 -0
- package/dist/document-changes.d.ts +11 -1
- package/dist/document-changes.d.ts.map +1 -1
- package/dist/document-changes.js +1 -1
- package/dist/document-changes.js.map +1 -1
- package/dist/drive-search.d.ts +4 -3
- package/dist/drive-search.d.ts.map +1 -1
- package/dist/drive-search.js +7 -7
- package/dist/drive-search.js.map +1 -1
- package/dist/file-changes.d.ts +52 -0
- package/dist/file-changes.d.ts.map +1 -0
- package/dist/file-changes.js +82 -0
- package/dist/file-changes.js.map +1 -0
- package/dist/prompts/change-sections.d.ts +11 -0
- package/dist/prompts/change-sections.d.ts.map +1 -0
- package/dist/prompts/change-sections.js +47 -0
- package/dist/prompts/change-sections.js.map +1 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +18 -34
- package/dist/prompts/index.js.map +1 -1
- package/dist/resources/changes.d.ts.map +1 -1
- package/dist/resources/changes.js +19 -10
- package/dist/resources/changes.js.map +1 -1
- package/dist/resources/orchestration-guide.md +1 -1
- package/dist/tools/{connect-microsoft.d.ts → account-link.d.ts} +9 -3
- package/dist/tools/account-link.d.ts.map +1 -0
- package/dist/tools/account-link.js +155 -0
- package/dist/tools/account-link.js.map +1 -0
- package/dist/tools/anchor-history-read.d.ts +25 -0
- package/dist/tools/anchor-history-read.d.ts.map +1 -0
- package/dist/tools/anchor-history-read.js +73 -0
- package/dist/tools/anchor-history-read.js.map +1 -0
- package/dist/tools/drive-search.contract.d.ts +7 -2
- package/dist/tools/drive-search.contract.d.ts.map +1 -1
- package/dist/tools/drive-search.contract.js +15 -7
- package/dist/tools/drive-search.contract.js.map +1 -1
- package/dist/tools/drive-search.d.ts.map +1 -1
- package/dist/tools/drive-search.js +16 -17
- package/dist/tools/drive-search.js.map +1 -1
- package/dist/tools/get-cell-history.d.ts +15 -0
- package/dist/tools/get-cell-history.d.ts.map +1 -1
- package/dist/tools/get-cell-history.js +67 -18
- package/dist/tools/get-cell-history.js.map +1 -1
- package/dist/tools/get-comments.d.ts.map +1 -1
- package/dist/tools/get-comments.js +2 -1
- package/dist/tools/get-comments.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -4
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-unenrolled-files.d.ts.map +1 -1
- package/dist/tools/list-unenrolled-files.js +18 -178
- package/dist/tools/list-unenrolled-files.js.map +1 -1
- package/dist/tools/list-unenrolled-render.d.ts +4 -0
- package/dist/tools/list-unenrolled-render.d.ts.map +1 -0
- package/dist/tools/list-unenrolled-render.js +219 -0
- package/dist/tools/list-unenrolled-render.js.map +1 -0
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +27 -57
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/write-comments.d.ts.map +1 -1
- package/dist/tools/write-comments.js +58 -2
- package/dist/tools/write-comments.js.map +1 -1
- package/dist/types.d.ts +79 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/zod-schemas.d.ts +57 -0
- package/dist/zod-schemas.d.ts.map +1 -1
- package/dist/zod-schemas.js +24 -1
- package/dist/zod-schemas.js.map +1 -1
- package/package.json +1 -1
- package/dist/tools/connect-microsoft.d.ts.map +0 -1
- package/dist/tools/connect-microsoft.js +0 -149
- package/dist/tools/connect-microsoft.js.map +0 -1
- package/dist/tools/document-changes-read.d.ts +0 -28
- package/dist/tools/document-changes-read.d.ts.map +0 -1
- package/dist/tools/document-changes-read.js +0 -34
- package/dist/tools/document-changes-read.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { renderEmpty, renderFound } from './list-unenrolled-render.js';
|
|
3
3
|
/**
|
|
4
4
|
* ENG-2785 — "which of my workbooks are NOT in Rockhopper yet?"
|
|
5
5
|
*
|
|
@@ -37,27 +37,23 @@ import { GRAPH_LINK_FAILURE_TEXT, graphLinkFailureFrom, } from '../graph-link-fa
|
|
|
37
37
|
*/
|
|
38
38
|
export function registerListUnenrolledFilesTool(server, api) {
|
|
39
39
|
server.registerTool('list_unenrolled_files', {
|
|
40
|
-
title: 'List
|
|
41
|
-
description: 'List the
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
|
|
48
|
-
// ENG-4283
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
|
|
57
|
-
// reaches Google Drive, so a model that lands here for a Google file
|
|
58
|
-
// has somewhere to go instead of reporting an empty drive.
|
|
59
|
-
'For a Google file, call `search_drive_files` with ' +
|
|
60
|
-
'provider="google" instead. ' +
|
|
40
|
+
title: 'List Files Not Yet in Rockhopper',
|
|
41
|
+
description: 'List the files Rockhopper has seen for this user that are NOT ' +
|
|
42
|
+
'enrolled — the answer to "what could I add?". Use this for browsing, ' +
|
|
43
|
+
'when the user cannot name a specific file; use `search_drive_files` ' +
|
|
44
|
+
'when they can. Read-only: it enrolls nothing and asks nothing. The ' +
|
|
45
|
+
'answer comes from stored records rather than a live read of the ' +
|
|
46
|
+
'user\'s drive, so it is dated. ' +
|
|
47
|
+
// ENG-6410 — scope stated up front, because the model has to know it
|
|
48
|
+
// BEFORE it picks the tool. ENG-4283 made this 'MICROSOFT ONLY' and
|
|
49
|
+
// ENG-4958 sent Google callers to `search_drive_files`; ENG-6409 gave
|
|
50
|
+
// the inventory a Google lane, so both are now false.
|
|
51
|
+
'It covers OneDrive and SharePoint (Microsoft) and Google Drive, for ' +
|
|
52
|
+
'whichever of those the user has connected, and every row names its ' +
|
|
53
|
+
'provider. To add a Microsoft row, pass its `msId` and `driveMsId` to ' +
|
|
54
|
+
'`enroll_file`; to add a Google row, pass its link to `enroll_file` as ' +
|
|
55
|
+
'`url`. An account with neither connected gets nothing from it, which ' +
|
|
56
|
+
'is not a statement about what that user has. ' +
|
|
61
57
|
// ENG-4271: an organisation's automatic-enrollment scope governs only
|
|
62
58
|
// which files its own background sweep enrolls on its own; it never
|
|
63
59
|
// stops `enroll_file` from adding any file listed here by hand.
|
|
@@ -130,160 +126,4 @@ export function registerListUnenrolledFilesTool(server, api) {
|
|
|
130
126
|
}
|
|
131
127
|
});
|
|
132
128
|
}
|
|
133
|
-
/**
|
|
134
|
-
* ENG-2814 — the "there is more" line, and it is never optional when a cursor
|
|
135
|
-
* came back.
|
|
136
|
-
*
|
|
137
|
-
* Written to the MODEL, not the user: it names the exact next call, because a
|
|
138
|
-
* hint that only says "more available" gets summarised away and the walk stops
|
|
139
|
-
* one page in.
|
|
140
|
-
*/
|
|
141
|
-
function moreToCome(nextCursor) {
|
|
142
|
-
if (!nextCursor)
|
|
143
|
-
return '';
|
|
144
|
-
return (`\n\nMORE FILES REMAIN — this is not the whole list. Call ` +
|
|
145
|
-
`\`list_unenrolled_files\` again with \`cursor="${nextCursor}"\`, and ` +
|
|
146
|
-
'keep going until a response comes back with no cursor. Do not tell the ' +
|
|
147
|
-
'user what they do or do not have until then. The cursor stops working 30 ' +
|
|
148
|
-
'minutes after the first page; if it is refused as expired, start again ' +
|
|
149
|
-
'from the first page rather than retrying it.');
|
|
150
|
-
}
|
|
151
|
-
function renderFound(items, freshness, nextCursor = null) {
|
|
152
|
-
const lines = items.map((item) => {
|
|
153
|
-
const where = item.parentPath ? ` in ${item.parentPath}` : '';
|
|
154
|
-
const modified = item.lastModifiedAt
|
|
155
|
-
? `, modified ${item.lastModifiedAt}`
|
|
156
|
-
: '';
|
|
157
|
-
// `hidden` means the user REMOVED this file from Rockhopper before. Its
|
|
158
|
-
// history is still held, so enrolling restores rather than duplicates —
|
|
159
|
-
// a different next step from a file that was never added, and the only
|
|
160
|
-
// thing distinguishing the two.
|
|
161
|
-
const previously = item.enrollmentState === 'hidden' ? ' [previously removed]' : '';
|
|
162
|
-
return (`- **${item.name}**${where}${modified}${previously}\n` +
|
|
163
|
-
` msId: ${item.msId}, driveMsId: ${item.driveMsId}`);
|
|
164
|
-
});
|
|
165
|
-
// "on this page", never a bare count: with a cursor outstanding the number is
|
|
166
|
-
// a page size, and calling it a total is the same lie as the old 200-row
|
|
167
|
-
// ceiling that reported nothing about itself.
|
|
168
|
-
const counted = nextCursor
|
|
169
|
-
? `${items.length} workbook(s) not yet in Rockhopper on this page:`
|
|
170
|
-
: `${items.length} workbook(s) not yet in Rockhopper:`;
|
|
171
|
-
return (`${counted}\n\n` +
|
|
172
|
-
`${lines.join('\n')}\n\n${dateline(freshness)}${moreToCome(nextCursor)}`);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* ENG-4283 — the backend's code for "no Microsoft tenant on this account".
|
|
176
|
-
*
|
|
177
|
-
* Duplicated from `DriveInventoryFreshnessDto` rather than imported, for the
|
|
178
|
-
* same reason `graph-link-failure.ts` duplicates its four: this package ships
|
|
179
|
-
* over npm and takes no build dependency on the API tree. EXACT match, and no
|
|
180
|
-
* case folding — a value this package has not learned about is not "probably
|
|
181
|
-
* this one", and guessing at a spelling is precisely what left ENG-4311's
|
|
182
|
-
* comparison dead for a release.
|
|
183
|
-
*/
|
|
184
|
-
const NO_MICROSOFT_TENANT = 'NO_MICROSOFT_TENANT';
|
|
185
|
-
/**
|
|
186
|
-
* What to tell the assistant when the lane does not serve this account.
|
|
187
|
-
*
|
|
188
|
-
* Three rules, each answering a specific way the old message did harm:
|
|
189
|
-
* - **No retry language.** Nothing about this changes on a later call, and a
|
|
190
|
-
* model complies with "try again shortly" indefinitely.
|
|
191
|
-
* - **No `connect_microsoft`.** There is no tenant behind it for this user, so
|
|
192
|
-
* the link would send them to Microsoft to be refused (ENG-2614).
|
|
193
|
-
* - **Names the scope and the alternative**, so the answer is usable rather
|
|
194
|
-
* than only correct — a Google customer's files are enrolled a different way
|
|
195
|
-
* and the assistant should say so instead of stopping at a refusal.
|
|
196
|
-
*/
|
|
197
|
-
const NO_MICROSOFT_TENANT_TEXT = 'Rockhopper\'s drive inventory covers OneDrive and SharePoint, and this ' +
|
|
198
|
-
'account has no Microsoft link — so there is nothing for it to list and ' +
|
|
199
|
-
'nothing to retry. This is NOT evidence that every workbook is already in ' +
|
|
200
|
-
'Rockhopper, and it does not mean the user has no files: it means this ' +
|
|
201
|
-
'particular list cannot see them. Do not call `list_unenrolled_files` again ' +
|
|
202
|
-
'for this user, and do not report their drive as covered. If they use ' +
|
|
203
|
-
'Google Workspace, their spreadsheets are added through Rockhopper directly ' +
|
|
204
|
-
'rather than from this list.';
|
|
205
|
-
function renderEmpty(freshness, nextCursor = null) {
|
|
206
|
-
// ORDER IS LOAD-BEARING. A broken link and a first refresh that has not
|
|
207
|
-
// finished both mean there is nothing to page THROUGH, so sending the model
|
|
208
|
-
// after another page instead of `connect_microsoft` starts a loop that cannot
|
|
209
|
-
// end. Those two branches come first and keep their own instruction.
|
|
210
|
-
//
|
|
211
|
-
// ENG-4311 — this used to compare `lastFailureReason === 'no_delegated_token'`
|
|
212
|
-
// against a producer that writes the SCREAMING_SNAKE `GraphLinkFailure` enum,
|
|
213
|
-
// so it never matched and this branch was dead: a user with no working link
|
|
214
|
-
// fell through to the never-refreshed message below and was told a scan had
|
|
215
|
-
// been started and to try again shortly. Neither was true. The four codes are
|
|
216
|
-
// kept APART rather than collapsed, because three of them name a different
|
|
217
|
-
// person who has to act — see `graph-link-failure.ts`.
|
|
218
|
-
// ENG-4283 — FIRST, ahead of every other branch including the link failures.
|
|
219
|
-
//
|
|
220
|
-
// The four states below all assume this lane COVERS the caller and something
|
|
221
|
-
// went wrong on the way to filling it. An account with no Microsoft tenant is
|
|
222
|
-
// a fifth thing: the refresh returns before it writes a sync-state row, so
|
|
223
|
-
// `asOf` stays null and `refreshing` stays false PERMANENTLY, and the
|
|
224
|
-
// never-refreshed branch below fires on every call — asserting a scan "has
|
|
225
|
-
// been started" when none was, and instructing a retry that cannot terminate.
|
|
226
|
-
//
|
|
227
|
-
// It outranks the link failures rather than following them because a link
|
|
228
|
-
// failure names something a user or an administrator can fix, and this names
|
|
229
|
-
// something neither can: sending a Google-only customer to
|
|
230
|
-
// `connect_microsoft` is ENG-2614's loop with an extra hop.
|
|
231
|
-
if (freshness.inapplicableReason === NO_MICROSOFT_TENANT) {
|
|
232
|
-
return NO_MICROSOFT_TENANT_TEXT;
|
|
233
|
-
}
|
|
234
|
-
const linkFailure = graphLinkFailureFrom(freshness.lastFailureReason);
|
|
235
|
-
if (linkFailure)
|
|
236
|
-
return GRAPH_LINK_FAILURE_TEXT[linkFailure];
|
|
237
|
-
if (!freshness.asOf) {
|
|
238
|
-
return ('No answer yet — the first scan of this user\'s drive has not finished. ' +
|
|
239
|
-
`${freshness.refreshing ? 'One is running now.' : 'One has been started.'} ` +
|
|
240
|
-
'Try again shortly. This is NOT evidence that every workbook is already ' +
|
|
241
|
-
`in Rockhopper.${failureNote(freshness)}`);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* ENG-2814 — AN EMPTY PAGE WITH A CURSOR IS THE MIDDLE OF A WALK.
|
|
245
|
-
*
|
|
246
|
-
* The backend filters enrolled rows out AFTER cutting a chunk and stops at a
|
|
247
|
-
* per-request scan budget, so "this page had none" is routine and says
|
|
248
|
-
* nothing about the drive. Reporting it as "everything is already covered" is
|
|
249
|
-
* the exact failure this whole tool was written against — an empty list that
|
|
250
|
-
* reads as reassurance — and pagination gives it a brand new way to happen.
|
|
251
|
-
*/
|
|
252
|
-
if (nextCursor) {
|
|
253
|
-
return ('No un-enrolled workbooks on this page — but the search is NOT ' +
|
|
254
|
-
'finished, and this says nothing yet about what the user has.' +
|
|
255
|
-
`${moreToCome(nextCursor)}`);
|
|
256
|
-
}
|
|
257
|
-
if (freshness.consecutiveFailures > 0) {
|
|
258
|
-
return ('No un-enrolled workbooks in the stored list, but the list is not ' +
|
|
259
|
-
`trustworthy right now.${failureNote(freshness)}\n\n${dateline(freshness)}`);
|
|
260
|
-
}
|
|
261
|
-
return ('Every workbook Rockhopper has seen for this user is already in ' +
|
|
262
|
-
`Rockhopper.\n\n${dateline(freshness)}`);
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* How old the answer is, on every branch that has an answer.
|
|
266
|
-
*
|
|
267
|
-
* Never omitted: the rows are stored, so a surface that does not date them
|
|
268
|
-
* invites a user to conclude a file is absent from their drive when it is only
|
|
269
|
-
* absent from our last refresh.
|
|
270
|
-
*/
|
|
271
|
-
function dateline(freshness) {
|
|
272
|
-
const asOf = freshness.asOf
|
|
273
|
-
? `As of ${freshness.asOf}`
|
|
274
|
-
: 'Never successfully refreshed';
|
|
275
|
-
const stale = freshness.stale ? ' (stale)' : '';
|
|
276
|
-
const refreshing = freshness.refreshing
|
|
277
|
-
? ' A refresh is running; call again for a newer answer.'
|
|
278
|
-
: '';
|
|
279
|
-
return (`${asOf}${stale} — from Rockhopper's stored records, not a live ` +
|
|
280
|
-
`Microsoft read.${refreshing}${failureNote(freshness)}`);
|
|
281
|
-
}
|
|
282
|
-
function failureNote(freshness) {
|
|
283
|
-
if (!freshness.consecutiveFailures || !freshness.lastFailureAt)
|
|
284
|
-
return '';
|
|
285
|
-
return (` Refresh has failed ${freshness.consecutiveFailures} time(s) in a row, ` +
|
|
286
|
-
`last at ${freshness.lastFailureAt}` +
|
|
287
|
-
`${freshness.lastFailureReason ? ` (${freshness.lastFailureReason})` : ''}.`);
|
|
288
|
-
}
|
|
289
129
|
//# sourceMappingURL=list-unenrolled-files.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-unenrolled-files.js","sourceRoot":"","sources":["../../src/tools/list-unenrolled-files.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,
|
|
1
|
+
{"version":3,"file":"list-unenrolled-files.js","sourceRoot":"","sources":["../../src/tools/list-unenrolled-files.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,+BAA+B,CAC7C,MAAiB,EACjB,GAAc;IAEd,MAAM,CAAC,YAAY,CACjB,uBAAuB,EACvB;QACE,KAAK,EAAE,kCAAkC;QACzC,WAAW,EACT,gEAAgE;YAChE,uEAAuE;YACvE,sEAAsE;YACtE,qEAAqE;YACrE,kEAAkE;YAClE,iCAAiC;YACjC,qEAAqE;YACrE,oEAAoE;YACpE,sEAAsE;YACtE,sDAAsD;YACtD,sEAAsE;YACtE,qEAAqE;YACrE,uEAAuE;YACvE,wEAAwE;YACxE,uEAAuE;YACvE,+CAA+C;YAC/C,sEAAsE;YACtE,oEAAoE;YACpE,gEAAgE;YAChE,kEAAkE;YAClE,sEAAsE;YACtE,gEAAgE;YAChE,uEAAuE;YACvE,0DAA0D;YAC1D,qEAAqE;YACrE,oEAAoE;YACpE,kEAAkE;YAClE,oEAAoE;YACpE,qEAAqE;YACrE,gEAAgE;YAChE,uEAAuE;QACzE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,GAAG,CAAC;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,4DAA4D;gBAC1D,gEAAgE;gBAChE,kCAAkC,CACrC;YACH,MAAM,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CACP,6DAA6D;gBAC3D,4DAA4D;gBAC5D,iEAAiE;gBACjE,4DAA4D,CAC/D;SACJ,CAAC;QACF,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,MAAM,GAAG,CAAC,kBAAkB,CAAC;gBACpE,UAAU,EAAE,cAAc;gBAC1B,KAAK;gBACL,MAAM;aACP,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,KAAK,CAAC,MAAM;4BAChB,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC;4BAC3C,CAAC,CAAC,WAAW,CAAC,SAAS,EAAE,UAAU,CAAC;qBACvC;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,uEAAuE;YACvE,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EACF,oDAAoD;4BACpD,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI;4BAC7D,6DAA6D;4BAC7D,oCAAoC;qBACvC;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DriveInventoryFreshness, DriveInventoryItem } from '../types.js';
|
|
2
|
+
export declare function renderFound(items: readonly DriveInventoryItem[], freshness: DriveInventoryFreshness, nextCursor?: string | null): string;
|
|
3
|
+
export declare function renderEmpty(freshness: DriveInventoryFreshness, nextCursor?: string | null): string;
|
|
4
|
+
//# sourceMappingURL=list-unenrolled-render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-unenrolled-render.d.ts","sourceRoot":"","sources":["../../src/tools/list-unenrolled-render.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,uBAAuB,EACvB,kBAAkB,EAGnB,MAAM,aAAa,CAAC;AA4GrB,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,SAAS,EAAE,uBAAuB,EAClC,UAAU,GAAE,MAAM,GAAG,IAAW,GAC/B,MAAM,CA0BR;AAmDD,wBAAgB,WAAW,CACzB,SAAS,EAAE,uBAAuB,EAClC,UAAU,GAAE,MAAM,GAAG,IAAW,GAC/B,MAAM,CA2CR"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { GRAPH_LINK_FAILURE_TEXT, graphLinkFailureFrom, } from '../graph-link-failure.js';
|
|
2
|
+
/**
|
|
3
|
+
* ENG-6410 — what `list_unenrolled_files` prints, derived from the backend's
|
|
4
|
+
* per-provider freshness rather than from a hardcoded provider.
|
|
5
|
+
*
|
|
6
|
+
* Moved out of `list-unenrolled-files.ts` so that file holds the tool's
|
|
7
|
+
* contract and this one holds its words. The Microsoft-only output is
|
|
8
|
+
* byte-identical to what shipped before the Google lane existed; that is
|
|
9
|
+
* pinned by `tools.list-unenrolled.google.test.ts`.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* ENG-4283 / ENG-6409 — the backend's codes for "this lane does not serve this
|
|
13
|
+
* account". Duplicated rather than imported: this package ships over npm and
|
|
14
|
+
* takes no build dependency on the API tree. EXACT match — an unknown value
|
|
15
|
+
* falls through rather than being guessed at (ENG-4311).
|
|
16
|
+
*/
|
|
17
|
+
const INAPPLICABLE = {
|
|
18
|
+
NO_MICROSOFT_TENANT: { covers: 'OneDrive and SharePoint', lacks: 'no Microsoft link' },
|
|
19
|
+
NO_GOOGLE_GRANT: { covers: 'Google Drive', lacks: 'no Google connection' },
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Every lane the backend reported. A backend older than ENG-6409 sends no
|
|
23
|
+
* `providers` and served Microsoft alone, so its top level IS that one lane.
|
|
24
|
+
*/
|
|
25
|
+
function lanesOf(freshness) {
|
|
26
|
+
if (freshness.providers?.length)
|
|
27
|
+
return freshness.providers;
|
|
28
|
+
const { providers: _absent, ...top } = freshness;
|
|
29
|
+
return [{ ...top, provider: 'microsoft' }];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The lanes that serve this caller — the ones its rows can come from. A code
|
|
33
|
+
* this package does not know is NOT read as "unserved" (ENG-4311: match the
|
|
34
|
+
* known codes exactly and fall through on anything else).
|
|
35
|
+
*/
|
|
36
|
+
function servedLanes(freshness) {
|
|
37
|
+
return lanesOf(freshness).filter((lane) => !lane.inapplicableReason || !INAPPLICABLE[lane.inapplicableReason]);
|
|
38
|
+
}
|
|
39
|
+
/** Total over the provider union: a new provider fails to compile here. */
|
|
40
|
+
function readerName(provider) {
|
|
41
|
+
switch (provider) {
|
|
42
|
+
case 'microsoft':
|
|
43
|
+
return 'Microsoft';
|
|
44
|
+
case 'google':
|
|
45
|
+
return 'Google';
|
|
46
|
+
default: {
|
|
47
|
+
const unhandled = provider;
|
|
48
|
+
return String(unhandled);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* "workbook" while only Microsoft serves the caller, exactly as before; "file"
|
|
54
|
+
* once a Google lane does, because that lane lists Docs and Slides too.
|
|
55
|
+
*/
|
|
56
|
+
function noun(freshness) {
|
|
57
|
+
return servedLanes(freshness).some((lane) => lane.provider === 'google')
|
|
58
|
+
? 'file'
|
|
59
|
+
: 'workbook';
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* ENG-2814 — the "there is more" line, and it is never optional when a cursor
|
|
63
|
+
* came back. Written to the MODEL: it names the exact next call, because a
|
|
64
|
+
* hint that only says "more available" gets summarised away.
|
|
65
|
+
*/
|
|
66
|
+
function moreToCome(nextCursor) {
|
|
67
|
+
if (!nextCursor)
|
|
68
|
+
return '';
|
|
69
|
+
return (`\n\nMORE FILES REMAIN — this is not the whole list. Call ` +
|
|
70
|
+
`\`list_unenrolled_files\` again with \`cursor="${nextCursor}"\`, and ` +
|
|
71
|
+
'keep going until a response comes back with no cursor. Do not tell the ' +
|
|
72
|
+
'user what they do or do not have until then. The cursor stops working 30 ' +
|
|
73
|
+
'minutes after the first page; if it is refused as expired, start again ' +
|
|
74
|
+
'from the first page rather than retrying it.');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The identity line `enroll_file` can act on, in the row's own provider's
|
|
78
|
+
* terms. A Microsoft row keeps its id pair. A Google row has no drive id and
|
|
79
|
+
* `enroll_file` takes a Google file only by its link, so the link is the id
|
|
80
|
+
* printed; with no link recorded the row says so rather than composing one.
|
|
81
|
+
*/
|
|
82
|
+
function identityLine(item) {
|
|
83
|
+
const provider = item.provider ?? 'microsoft';
|
|
84
|
+
switch (provider) {
|
|
85
|
+
case 'microsoft':
|
|
86
|
+
return ` msId: ${item.msId}, driveMsId: ${item.driveMsId}`;
|
|
87
|
+
case 'google':
|
|
88
|
+
return item.webUrl
|
|
89
|
+
? ` provider: google, fileId: ${item.msId} — enroll with url: ${item.webUrl}`
|
|
90
|
+
: ` provider: google, fileId: ${item.msId} — no link recorded; ask ` +
|
|
91
|
+
'the user for its link and pass that to `enroll_file` as `url`';
|
|
92
|
+
default: {
|
|
93
|
+
const unhandled = provider;
|
|
94
|
+
return ` provider: ${String(unhandled)}, fileId: ${item.msId}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function renderFound(items, freshness, nextCursor = null) {
|
|
99
|
+
const lines = items.map((item) => {
|
|
100
|
+
const where = item.parentPath ? ` in ${item.parentPath}` : '';
|
|
101
|
+
const modified = item.lastModifiedAt
|
|
102
|
+
? `, modified ${item.lastModifiedAt}`
|
|
103
|
+
: '';
|
|
104
|
+
// `hidden` means the user REMOVED this file before; enrolling restores
|
|
105
|
+
// rather than duplicates, the only thing distinguishing it from new.
|
|
106
|
+
const previously = item.enrollmentState === 'hidden' ? ' [previously removed]' : '';
|
|
107
|
+
return (`- **${item.name}**${where}${modified}${previously}\n` +
|
|
108
|
+
identityLine(item));
|
|
109
|
+
});
|
|
110
|
+
// "on this page", never a bare count: with a cursor outstanding the number
|
|
111
|
+
// is a page size, not a total.
|
|
112
|
+
const counted = nextCursor
|
|
113
|
+
? `${items.length} ${noun(freshness)}(s) not yet in Rockhopper on this page:`
|
|
114
|
+
: `${items.length} ${noun(freshness)}(s) not yet in Rockhopper:`;
|
|
115
|
+
return (`${counted}\n\n` +
|
|
116
|
+
`${lines.join('\n')}\n\n${dateline(freshness)}${moreToCome(nextCursor)}`);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* No lane serves the account. No retry language (nothing changes on a later
|
|
120
|
+
* call), no `connect_microsoft` (ENG-2614's loop), and the scope named from
|
|
121
|
+
* the lanes the backend reported rather than assumed.
|
|
122
|
+
*/
|
|
123
|
+
function inapplicableText(freshness) {
|
|
124
|
+
const known = lanesOf(freshness)
|
|
125
|
+
.map((lane) => INAPPLICABLE[lane.inapplicableReason ?? ''])
|
|
126
|
+
.filter((entry) => entry !== undefined);
|
|
127
|
+
return (`Rockhopper's drive inventory covers ${known.map((k) => k.covers).join(' and ')}, ` +
|
|
128
|
+
`and this account has ${known.map((k) => k.lacks).join(' and ')} — so ` +
|
|
129
|
+
'there is nothing for it to list and nothing to retry. This is NOT ' +
|
|
130
|
+
'evidence that every file is already in Rockhopper, and it does not mean ' +
|
|
131
|
+
'the user has no files: it means this particular list cannot see them. ' +
|
|
132
|
+
'Do not call `list_unenrolled_files` again for this user, and do not ' +
|
|
133
|
+
'report their drive as covered. A file the user can link to is still ' +
|
|
134
|
+
'added with `enroll_file` and that link.');
|
|
135
|
+
}
|
|
136
|
+
/** A Google lane whose refresh could not use the user's Google credential. */
|
|
137
|
+
const GOOGLE_LINK_FAILURE_TEXT = 'Rockhopper cannot see this user\'s Google Drive files: their Google ' +
|
|
138
|
+
'account is not connected, or its connection has expired. Ask the user to ' +
|
|
139
|
+
'connect Google Drive in Rockhopper Settings; do not compose a sign-in link ' +
|
|
140
|
+
'yourself. This list will stay empty until that is done, which is NOT the ' +
|
|
141
|
+
'same as having nothing to add.';
|
|
142
|
+
/** The first serving lane whose last refresh failed on its link, as text. */
|
|
143
|
+
function linkFailureText(freshness) {
|
|
144
|
+
for (const lane of servedLanes(freshness)) {
|
|
145
|
+
const failure = graphLinkFailureFrom(lane.lastFailureReason);
|
|
146
|
+
if (!failure)
|
|
147
|
+
continue;
|
|
148
|
+
switch (lane.provider) {
|
|
149
|
+
case 'microsoft':
|
|
150
|
+
return GRAPH_LINK_FAILURE_TEXT[failure];
|
|
151
|
+
case 'google':
|
|
152
|
+
return GOOGLE_LINK_FAILURE_TEXT;
|
|
153
|
+
default: {
|
|
154
|
+
const unhandled = lane.provider;
|
|
155
|
+
return String(unhandled);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
export function renderEmpty(freshness, nextCursor = null) {
|
|
162
|
+
// ORDER IS LOAD-BEARING (ENG-4283, ENG-4311, ENG-2814). An account no lane
|
|
163
|
+
// serves comes first: every later branch assumes a lane covers the caller,
|
|
164
|
+
// and its never-refreshed branch would otherwise claim a scan "has been
|
|
165
|
+
// started" forever. A broken link and an unfinished first refresh come next,
|
|
166
|
+
// because neither has anything to page THROUGH.
|
|
167
|
+
if (freshness.inapplicableReason && INAPPLICABLE[freshness.inapplicableReason]) {
|
|
168
|
+
return inapplicableText(freshness);
|
|
169
|
+
}
|
|
170
|
+
const linkFailure = linkFailureText(freshness);
|
|
171
|
+
if (linkFailure)
|
|
172
|
+
return linkFailure;
|
|
173
|
+
if (!freshness.asOf) {
|
|
174
|
+
return ('No answer yet — the first scan of this user\'s drive has not finished. ' +
|
|
175
|
+
`${freshness.refreshing ? 'One is running now.' : 'One has been started.'} ` +
|
|
176
|
+
'Try again shortly. This is NOT evidence that every workbook is already ' +
|
|
177
|
+
`in Rockhopper.${failureNote(freshness)}`);
|
|
178
|
+
}
|
|
179
|
+
// ENG-2814 — an empty page WITH a cursor is the middle of a walk: the
|
|
180
|
+
// backend filters after cutting a chunk and stops at a scan budget.
|
|
181
|
+
if (nextCursor) {
|
|
182
|
+
return ('No un-enrolled workbooks on this page — but the search is NOT ' +
|
|
183
|
+
'finished, and this says nothing yet about what the user has.' +
|
|
184
|
+
`${moreToCome(nextCursor)}`);
|
|
185
|
+
}
|
|
186
|
+
if (freshness.consecutiveFailures > 0) {
|
|
187
|
+
return ('No un-enrolled workbooks in the stored list, but the list is not ' +
|
|
188
|
+
`trustworthy right now.${failureNote(freshness)}\n\n${dateline(freshness)}`);
|
|
189
|
+
}
|
|
190
|
+
return (`Every ${noun(freshness)} Rockhopper has seen for this user is already in ` +
|
|
191
|
+
`Rockhopper.\n\n${dateline(freshness)}`);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* How old the answer is, on every branch that has an answer, and which
|
|
195
|
+
* providers it was read from — never omitted, because the rows are stored.
|
|
196
|
+
*/
|
|
197
|
+
function dateline(freshness) {
|
|
198
|
+
const asOf = freshness.asOf
|
|
199
|
+
? `As of ${freshness.asOf}`
|
|
200
|
+
: 'Never successfully refreshed';
|
|
201
|
+
const stale = freshness.stale ? ' (stale)' : '';
|
|
202
|
+
const refreshing = freshness.refreshing
|
|
203
|
+
? ' A refresh is running; call again for a newer answer.'
|
|
204
|
+
: '';
|
|
205
|
+
const served = servedLanes(freshness);
|
|
206
|
+
const readers = (served.length ? served : lanesOf(freshness))
|
|
207
|
+
.map((lane) => readerName(lane.provider))
|
|
208
|
+
.join(' or ');
|
|
209
|
+
return (`${asOf}${stale} — from Rockhopper's stored records, not a live ` +
|
|
210
|
+
`${readers} read.${refreshing}${failureNote(freshness)}`);
|
|
211
|
+
}
|
|
212
|
+
function failureNote(freshness) {
|
|
213
|
+
if (!freshness.consecutiveFailures || !freshness.lastFailureAt)
|
|
214
|
+
return '';
|
|
215
|
+
return (` Refresh has failed ${freshness.consecutiveFailures} time(s) in a row, ` +
|
|
216
|
+
`last at ${freshness.lastFailureAt}` +
|
|
217
|
+
`${freshness.lastFailureReason ? ` (${freshness.lastFailureReason})` : ''}.`);
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=list-unenrolled-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-unenrolled-render.js","sourceRoot":"","sources":["../../src/tools/list-unenrolled-render.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAMlD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,SAAS,GACb,IAAI,CAmSN"}
|
package/dist/tools/search.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { isNotReady, notReadyToolResult } from '../not-ready.js';
|
|
3
|
+
import { formatDocumentChanges } from '../document-changes.js';
|
|
4
|
+
import { fileChangesUnavailableText, readFileChanges } from '../file-changes.js';
|
|
5
|
+
import { isUnknownSheet, unknownSheetToolResult } from '../sheet-catalogue.js';
|
|
6
6
|
export function registerSearchTool(server, api) {
|
|
7
7
|
server.registerTool('search_files', {
|
|
8
8
|
title: 'Search Files',
|
|
@@ -151,55 +151,31 @@ export function registerSearchTool(server, api) {
|
|
|
151
151
|
},
|
|
152
152
|
}, async ({ fileMsId, sheetName, cursor }) => {
|
|
153
153
|
try {
|
|
154
|
-
// ENG-5397 — WHICH CHANGE MODEL, before which reader
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
reason: 'unknown_file_type',
|
|
166
|
-
fileMsId,
|
|
167
|
-
fileName: file.name,
|
|
168
|
-
});
|
|
154
|
+
// ENG-5397 — WHICH CHANGE MODEL, before which reader: the spreadsheet
|
|
155
|
+
// lanes cannot hold a document row, so a `.docx` read `[]` from them by
|
|
156
|
+
// construction. ENG-6431 — that dispatch (plus the fold, enrolment and
|
|
157
|
+
// sheet-existence gates) lives in `readFileChanges`, shared with the
|
|
158
|
+
// `…/changes` resource and the prompts; this tool only formats.
|
|
159
|
+
const read = await readFileChanges(api, fileMsId, { sheetName, cursor });
|
|
160
|
+
if (read.kind === 'unavailable') {
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: 'text', text: fileChangesUnavailableText(read) }],
|
|
163
|
+
isError: true,
|
|
164
|
+
};
|
|
169
165
|
}
|
|
170
|
-
if (
|
|
171
|
-
return
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
if (read.kind === 'document') {
|
|
167
|
+
return {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: 'text',
|
|
171
|
+
text: formatDocumentChanges(read.response, read.file.name),
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
177
175
|
}
|
|
178
|
-
|
|
179
|
-
// retracts and rewrites the uncommitted window, so a pending fold
|
|
180
|
-
// means this list is mid-rewrite in either shape.
|
|
181
|
-
await assertChangeHistoryComplete(api, fileMsId);
|
|
182
|
-
if (sheetName) {
|
|
176
|
+
if (read.kind === 'spreadsheet_sheet') {
|
|
183
177
|
// Sheet-filtered mode: bounded by sheet size, no pagination needed.
|
|
184
|
-
|
|
185
|
-
// returned 9 rows clean. This is the practical workaround when the
|
|
186
|
-
// caller already knows which sheet to drill into.
|
|
187
|
-
const changes = await api.getUnattributedChangesBySheet(fileMsId, sheetName);
|
|
188
|
-
// ENG-2824: an empty answer is the only ambiguous one, so it is the
|
|
189
|
-
// only one that pays for the version read. A file still being read
|
|
190
|
-
// for the first time has no versions and no parsed cells, and the
|
|
191
|
-
// fold probe above cannot see that state — enrolment enqueues no
|
|
192
|
-
// fold. See `assertEnrollmentComplete`.
|
|
193
|
-
if (changes.length === 0) {
|
|
194
|
-
assertEnrollmentComplete(fileMsId, await api.getFileVersions(fileMsId));
|
|
195
|
-
// ENG-4347 — and the same trade one ambiguity over: only an empty
|
|
196
|
-
// answer asserts something this tool has not checked. A row that
|
|
197
|
-
// came back names its own sheet, so a non-empty answer has already
|
|
198
|
-
// proved what this goes and asks. Enrolment is settled FIRST above
|
|
199
|
-
// because a file whose initial read has not landed has no
|
|
200
|
-
// catalogue either, and "still reading" is the truer answer.
|
|
201
|
-
await assertSheetExists(api, file, sheetName);
|
|
202
|
-
}
|
|
178
|
+
const { changes } = read;
|
|
203
179
|
const body = formatChangeRows(changes);
|
|
204
180
|
return {
|
|
205
181
|
content: [
|
|
@@ -217,14 +193,8 @@ export function registerSearchTool(server, api) {
|
|
|
217
193
|
// 25k-token client limit (audit measured one file at 12.5 MB / 28k
|
|
218
194
|
// rows on the unpaginated route — KI-097).
|
|
219
195
|
const MAX_DISPLAYED = 200;
|
|
220
|
-
const page =
|
|
196
|
+
const { page } = read;
|
|
221
197
|
if (page.changes.length === 0) {
|
|
222
|
-
// ENG-2824 — same rule as the sheet mode above. `totalCount === 0`
|
|
223
|
-
// over a file whose initial read has not landed is a premature
|
|
224
|
-
// absence, not a fact about the workbook.
|
|
225
|
-
if (page.totalCount === 0) {
|
|
226
|
-
assertEnrollmentComplete(fileMsId, await api.getFileVersions(fileMsId));
|
|
227
|
-
}
|
|
228
198
|
return {
|
|
229
199
|
content: [
|
|
230
200
|
{
|