From f71e37b2e37d455db19e3f4d34a7c7684e390e16 Mon Sep 17 00:00:00 2001 From: Allen Hutchison Date: Wed, 29 Apr 2026 11:15:13 -0700 Subject: [PATCH 1/4] fix(docs): resolve "comment-specific fields" error in docs.getText Removed unsupported includeComments parameter from documents.get and refined the field mask to avoid broad 'tabs' request which triggered internal API conflicts. Added PREVIEW_WITHOUT_SUGGESTIONS view mode to getText for cleaner extraction. - Fix DocsService.getText invalid parameter - Use specific field mask for tabs in DocsService - Update documentation to correctly reference drive.getComments - Update test expectations for DocsService --- skills/google-docs/SKILL.md | 4 ++-- .../src/__tests__/services/DocsService.test.ts | 6 ++++-- workspace-server/src/services/DocsService.ts | 12 ++++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/skills/google-docs/SKILL.md b/skills/google-docs/SKILL.md index 806073e6..eb0d1592 100644 --- a/skills/google-docs/SKILL.md +++ b/skills/google-docs/SKILL.md @@ -236,7 +236,7 @@ the destination by folder ID or folder name. ### Reading Comments -Use `docs.getComments` to retrieve all comments on a document: +Use `drive.getComments` to retrieve all comments on a document: - Returns comment threads with author, content, timestamp, and resolution status - Includes **threaded replies** with author, content, timestamp, and action @@ -244,7 +244,7 @@ Use `docs.getComments` to retrieve all comments on a document: - Includes **quoted file content** showing what text the comment is anchored to ``` -docs.getComments({ documentId: "doc-id" }) +drive.getComments({ fileId: "doc-id" }) ``` ### Reading Suggestions diff --git a/workspace-server/src/__tests__/services/DocsService.test.ts b/workspace-server/src/__tests__/services/DocsService.test.ts index 7e4461c0..a614293b 100644 --- a/workspace-server/src/__tests__/services/DocsService.test.ts +++ b/workspace-server/src/__tests__/services/DocsService.test.ts @@ -845,7 +845,8 @@ describe('DocsService', () => { expect(mockDocsAPI.documents.get).toHaveBeenCalledWith({ documentId: 'test-doc-id', - fields: 'tabs', + fields: + 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', includeTabsContent: true, }); @@ -922,7 +923,8 @@ describe('DocsService', () => { expect(mockDocsAPI.documents.get).toHaveBeenCalledWith({ documentId: 'test-doc-id', - fields: 'tabs', + fields: + 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', includeTabsContent: true, }); diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index 06053f20..b43471d5 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -307,7 +307,8 @@ export class DocsService { // Discover the end index by reading the document (required for tabs) const res = await docs.documents.get({ documentId: id, - fields: 'tabs', + fields: + 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', includeTabsContent: true, }); @@ -543,9 +544,11 @@ export class DocsService { const docs = await this.getDocsClient(); const res = await docs.documents.get({ documentId: id, - fields: 'title,tabs', // Request title and tabs (body is legacy and mutually exclusive with tabs in mask) + fields: + 'title,tabs(tabProperties,documentTab(body,headers,footers,footnotes))', includeTabsContent: true, - }); + suggestionsViewMode: 'PREVIEW_WITHOUT_SUGGESTIONS', + } as any); const docTitle = res.data.title; const tabs = this._flattenTabs(res.data.tabs || []); @@ -736,7 +739,8 @@ export class DocsService { // Get the document to find where the text will be replaced const docBefore = await docs.documents.get({ documentId: id, - fields: 'tabs', + fields: + 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', includeTabsContent: true, }); From 4361935c5710fbaf7ba79432a0310b743522162c Mon Sep 17 00:00:00 2001 From: Allen Hutchison Date: Wed, 29 Apr 2026 11:27:01 -0700 Subject: [PATCH 2/4] refactor(docs): extract tabs field mask, drop unnecessary as-any cast - Extract the tabs field mask to a TABS_FIELD_MASK module export so the three documents.get call sites stay in sync. - Remove the `as any` cast on the getText documents.get call. The googleapis SDK type already accepts suggestionsViewMode (the existing getSuggestions call uses it without a cast); tsc 5.9.3 typechecks cleanly without it. - Update tests to reference the same constant. --- .../src/__tests__/services/DocsService.test.ts | 8 +++----- workspace-server/src/services/DocsService.ts | 17 ++++++++++------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/workspace-server/src/__tests__/services/DocsService.test.ts b/workspace-server/src/__tests__/services/DocsService.test.ts index a614293b..fc005815 100644 --- a/workspace-server/src/__tests__/services/DocsService.test.ts +++ b/workspace-server/src/__tests__/services/DocsService.test.ts @@ -12,7 +12,7 @@ import { beforeEach, afterEach, } from '@jest/globals'; -import { DocsService } from '../../services/DocsService'; +import { DocsService, TABS_FIELD_MASK } from '../../services/DocsService'; import { AuthManager } from '../../auth/AuthManager'; import { google } from 'googleapis'; @@ -845,8 +845,7 @@ describe('DocsService', () => { expect(mockDocsAPI.documents.get).toHaveBeenCalledWith({ documentId: 'test-doc-id', - fields: - 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', + fields: TABS_FIELD_MASK, includeTabsContent: true, }); @@ -923,8 +922,7 @@ describe('DocsService', () => { expect(mockDocsAPI.documents.get).toHaveBeenCalledWith({ documentId: 'test-doc-id', - fields: - 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', + fields: TABS_FIELD_MASK, includeTabsContent: true, }); diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts index b43471d5..72350bb7 100644 --- a/workspace-server/src/services/DocsService.ts +++ b/workspace-server/src/services/DocsService.ts @@ -11,6 +11,12 @@ import { extractDocId } from '../utils/IdUtils'; import { gaxiosOptions } from '../utils/GaxiosConfig'; import { extractDocumentId as validateAndExtractDocId } from '../utils/validation'; +// Field mask for documents.get when reading tab content. Selects only the +// structural fields we use; broader masks like 'tabs' alone trigger +// "comment-specific fields" errors when combined with includeTabsContent. +export const TABS_FIELD_MASK = + 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))'; + interface BaseDocsSuggestion { text: string; startIndex?: number; @@ -307,8 +313,7 @@ export class DocsService { // Discover the end index by reading the document (required for tabs) const res = await docs.documents.get({ documentId: id, - fields: - 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', + fields: TABS_FIELD_MASK, includeTabsContent: true, }); @@ -544,11 +549,10 @@ export class DocsService { const docs = await this.getDocsClient(); const res = await docs.documents.get({ documentId: id, - fields: - 'title,tabs(tabProperties,documentTab(body,headers,footers,footnotes))', + fields: `title,${TABS_FIELD_MASK}`, includeTabsContent: true, suggestionsViewMode: 'PREVIEW_WITHOUT_SUGGESTIONS', - } as any); + }); const docTitle = res.data.title; const tabs = this._flattenTabs(res.data.tabs || []); @@ -739,8 +743,7 @@ export class DocsService { // Get the document to find where the text will be replaced const docBefore = await docs.documents.get({ documentId: id, - fields: - 'tabs(tabProperties,documentTab(body,headers,footers,footnotes))', + fields: TABS_FIELD_MASK, includeTabsContent: true, }); From 6e7670fb2c4bd0d510b6d2a4883bd54a8bd1ff0b Mon Sep 17 00:00:00 2001 From: Allen Hutchison Date: Wed, 29 Apr 2026 11:29:47 -0700 Subject: [PATCH 3/4] test(docs): assert getText calls documents.get with refined args Locks in the new behavior so a future regression that drops the narrowed field mask or the PREVIEW_WITHOUT_SUGGESTIONS view mode is caught at PR time rather than only via API errors against real Google Docs. --- workspace-server/src/__tests__/services/DocsService.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/workspace-server/src/__tests__/services/DocsService.test.ts b/workspace-server/src/__tests__/services/DocsService.test.ts index fc005815..d3aa66e6 100644 --- a/workspace-server/src/__tests__/services/DocsService.test.ts +++ b/workspace-server/src/__tests__/services/DocsService.test.ts @@ -465,6 +465,12 @@ describe('DocsService', () => { const result = await docsService.getText({ documentId: 'test-doc-id' }); + expect(mockDocsAPI.documents.get).toHaveBeenCalledWith({ + documentId: 'test-doc-id', + fields: `title,${TABS_FIELD_MASK}`, + includeTabsContent: true, + suggestionsViewMode: 'PREVIEW_WITHOUT_SUGGESTIONS', + }); expect(result.content[0].text).toBe('Hello World\n'); }); From 0026cd3bd41d9643fa222a34c775ee62829786ba Mon Sep 17 00:00:00 2001 From: Allen Hutchison Date: Wed, 29 Apr 2026 11:39:16 -0700 Subject: [PATCH 4/4] fix(scripts): add tsconfig.json so print-scopes.ts compiles under TS 6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TS 6 became stricter about implicit rootDir. ts-node compiling scripts/print-scopes.ts against the root tsconfig (which only includes workspace-server/src/) failed with TS5011 — the file is outside the included tree, so TS couldn't infer rootDir. Add scripts/tsconfig.json with an explicit rootDir of "..", and pass --project scripts/tsconfig.json from both call sites (the drift-guard test in feature-config.test.ts and setup-gcp.sh) so ts-node uses it. Verified: full test suite (511 tests, 28 suites) green. --- scripts/setup-gcp.sh | 2 +- scripts/tsconfig.json | 7 +++++++ .../src/__tests__/features/feature-config.test.ts | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 scripts/tsconfig.json diff --git a/scripts/setup-gcp.sh b/scripts/setup-gcp.sh index 059fafc5..02ce4524 100755 --- a/scripts/setup-gcp.sh +++ b/scripts/setup-gcp.sh @@ -95,7 +95,7 @@ echo "" # workspace-server/src/features/feature-config.ts. See issue #323. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -SCOPES_OUTPUT=$(cd "$REPO_ROOT" && npx --no-install ts-node --transpile-only scripts/print-scopes.ts 2>&1) +SCOPES_OUTPUT=$(cd "$REPO_ROOT" && npx --no-install ts-node --transpile-only --project scripts/tsconfig.json scripts/print-scopes.ts 2>&1) if [ $? -ne 0 ]; then echo -e "${RED}Error: Failed to compute OAuth scopes from feature-config.ts.${NC}" echo -e "${RED}Did you run 'npm install' at the repo root?${NC}" diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 00000000..1e3ebcb8 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": ".." + }, + "include": ["**/*.ts", "../workspace-server/src/**/*"] +} diff --git a/workspace-server/src/__tests__/features/feature-config.test.ts b/workspace-server/src/__tests__/features/feature-config.test.ts index ce836670..04217be3 100644 --- a/workspace-server/src/__tests__/features/feature-config.test.ts +++ b/workspace-server/src/__tests__/features/feature-config.test.ts @@ -105,7 +105,7 @@ describe('getAllPossibleScopes (issue #323)', () => { // execSync (not execFileSync) so Windows can resolve npx.cmd via the // shell. Tests run on ubuntu/macos/windows. const output = execSync( - 'npx --no-install ts-node --transpile-only scripts/print-scopes.ts', + 'npx --no-install ts-node --transpile-only --project scripts/tsconfig.json scripts/print-scopes.ts', { cwd: repoRoot, encoding: 'utf8' }, ); const printed = output.trim().split(/\r?\n/);