@pan-sec/notebooklm-mcp 2026.3.3 → 2026.4.1
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/dist/auth/auth-manager.d.ts +0 -1
- package/dist/auth/auth-manager.js +0 -1
- package/dist/auth/mcp-auth.d.ts +0 -1
- package/dist/auth/mcp-auth.js +0 -1
- package/dist/compliance/alert-manager.d.ts +6 -2
- package/dist/compliance/alert-manager.js +40 -10
- package/dist/compliance/breach-detection.d.ts +0 -1
- package/dist/compliance/breach-detection.js +0 -1
- package/dist/compliance/change-log.d.ts +13 -1
- package/dist/compliance/change-log.js +82 -16
- package/dist/compliance/compliance-logger.d.ts +29 -3
- package/dist/compliance/compliance-logger.js +90 -27
- package/dist/compliance/compliance-tools.d.ts +0 -1
- package/dist/compliance/compliance-tools.js +0 -1
- package/dist/compliance/consent-manager.d.ts +0 -1
- package/dist/compliance/consent-manager.js +0 -1
- package/dist/compliance/dashboard.d.ts +4 -3
- package/dist/compliance/dashboard.js +11 -8
- package/dist/compliance/data-classification.d.ts +0 -1
- package/dist/compliance/data-classification.js +0 -1
- package/dist/compliance/data-erasure.d.ts +0 -1
- package/dist/compliance/data-erasure.js +0 -1
- package/dist/compliance/data-export.d.ts +0 -1
- package/dist/compliance/data-export.js +0 -1
- package/dist/compliance/data-inventory.d.ts +0 -1
- package/dist/compliance/data-inventory.js +0 -1
- package/dist/compliance/dsar-handler.d.ts +0 -1
- package/dist/compliance/dsar-handler.js +0 -1
- package/dist/compliance/evidence-collector.d.ts +0 -1
- package/dist/compliance/evidence-collector.js +4 -2
- package/dist/compliance/health-monitor.d.ts +0 -1
- package/dist/compliance/health-monitor.js +0 -1
- package/dist/compliance/incident-manager.d.ts +0 -1
- package/dist/compliance/incident-manager.js +0 -1
- package/dist/compliance/index.d.ts +0 -1
- package/dist/compliance/index.js +0 -1
- package/dist/compliance/policy-docs.d.ts +0 -1
- package/dist/compliance/policy-docs.js +0 -1
- package/dist/compliance/privacy-notice-text.d.ts +0 -1
- package/dist/compliance/privacy-notice-text.js +0 -1
- package/dist/compliance/privacy-notice.d.ts +0 -1
- package/dist/compliance/privacy-notice.js +0 -1
- package/dist/compliance/report-generator.d.ts +7 -1
- package/dist/compliance/report-generator.js +116 -34
- package/dist/compliance/retention-engine.d.ts +0 -1
- package/dist/compliance/retention-engine.js +0 -1
- package/dist/compliance/siem-exporter.d.ts +26 -2
- package/dist/compliance/siem-exporter.js +89 -24
- package/dist/compliance/types.d.ts +0 -1
- package/dist/compliance/types.js +0 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.js +2 -3
- package/dist/errors.d.ts +0 -1
- package/dist/errors.js +0 -1
- package/dist/events/event-emitter.d.ts +9 -1
- package/dist/events/event-emitter.js +47 -8
- package/dist/events/event-types.d.ts +0 -1
- package/dist/events/event-types.js +8 -2
- package/dist/gemini/gemini-client.d.ts +0 -1
- package/dist/gemini/gemini-client.js +237 -45
- package/dist/gemini/index.d.ts +0 -1
- package/dist/gemini/index.js +0 -1
- package/dist/gemini/pdf-chunker.d.ts +0 -1
- package/dist/gemini/pdf-chunker.js +60 -35
- package/dist/gemini/types.d.ts +0 -1
- package/dist/gemini/types.js +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +60 -7
- package/dist/library/notebook-library.d.ts +30 -2
- package/dist/library/notebook-library.js +345 -85
- package/dist/library/types.d.ts +0 -1
- package/dist/library/types.js +0 -1
- package/dist/logging/index.d.ts +0 -1
- package/dist/logging/index.js +0 -1
- package/dist/logging/query-logger.d.ts +20 -1
- package/dist/logging/query-logger.js +104 -21
- package/dist/notebook-creation/audio-manager.d.ts +7 -16
- package/dist/notebook-creation/audio-manager.js +115 -58
- package/dist/notebook-creation/browser-options.d.ts +0 -1
- package/dist/notebook-creation/browser-options.js +0 -1
- package/dist/notebook-creation/data-table-manager.d.ts +8 -14
- package/dist/notebook-creation/data-table-manager.js +64 -37
- package/dist/notebook-creation/dom-scripts.d.ts +0 -1
- package/dist/notebook-creation/dom-scripts.js +0 -1
- package/dist/notebook-creation/errors.d.ts +0 -1
- package/dist/notebook-creation/errors.js +0 -1
- package/dist/notebook-creation/index.d.ts +0 -1
- package/dist/notebook-creation/index.js +0 -1
- package/dist/notebook-creation/notebook-creator.d.ts +9 -1
- package/dist/notebook-creation/notebook-creator.js +50 -1
- package/dist/notebook-creation/notebook-nav.d.ts +0 -1
- package/dist/notebook-creation/notebook-nav.js +21 -6
- package/dist/notebook-creation/notebook-sync.d.ts +14 -2
- package/dist/notebook-creation/notebook-sync.js +124 -35
- package/dist/notebook-creation/selectors.d.ts +0 -1
- package/dist/notebook-creation/selectors.js +6 -4
- package/dist/notebook-creation/source-manager-shared.d.ts +75 -0
- package/dist/notebook-creation/source-manager-shared.js +86 -0
- package/dist/notebook-creation/source-manager.d.ts +29 -2
- package/dist/notebook-creation/source-manager.js +0 -0
- package/dist/notebook-creation/studio-manager-base.d.ts +57 -0
- package/dist/notebook-creation/studio-manager-base.js +67 -0
- package/dist/notebook-creation/types.d.ts +0 -1
- package/dist/notebook-creation/types.js +0 -1
- package/dist/notebook-creation/video-manager.d.ts +3 -16
- package/dist/notebook-creation/video-manager.js +94 -53
- package/dist/observability/metrics.d.ts +0 -1
- package/dist/observability/metrics.js +0 -1
- package/dist/quota/index.d.ts +0 -1
- package/dist/quota/index.js +0 -1
- package/dist/quota/quota-manager.d.ts +59 -4
- package/dist/quota/quota-manager.js +195 -46
- package/dist/resources/resource-handlers.d.ts +0 -1
- package/dist/resources/resource-handlers.js +33 -3
- package/dist/session/browser-session.d.ts +0 -1
- package/dist/session/browser-session.js +0 -1
- package/dist/session/session-manager.d.ts +0 -1
- package/dist/session/session-manager.js +0 -1
- package/dist/session/session-timeout.d.ts +0 -1
- package/dist/session/session-timeout.js +0 -1
- package/dist/session/shared-context-manager.d.ts +0 -1
- package/dist/session/shared-context-manager.js +0 -1
- package/dist/tools/annotations.d.ts +0 -1
- package/dist/tools/annotations.js +0 -1
- package/dist/tools/definitions/ask-question.d.ts +6 -3
- package/dist/tools/definitions/ask-question.js +12 -8
- package/dist/tools/definitions/chat-history.d.ts +0 -1
- package/dist/tools/definitions/chat-history.js +1 -1
- package/dist/tools/definitions/data-tables.d.ts +0 -1
- package/dist/tools/definitions/data-tables.js +4 -1
- package/dist/tools/definitions/gemini.d.ts +0 -1
- package/dist/tools/definitions/gemini.js +14 -7
- package/dist/tools/definitions/notebook-management.d.ts +0 -1
- package/dist/tools/definitions/notebook-management.js +7 -2
- package/dist/tools/definitions/query-history.d.ts +0 -1
- package/dist/tools/definitions/query-history.js +0 -1
- package/dist/tools/definitions/session-management.d.ts +0 -1
- package/dist/tools/definitions/session-management.js +0 -1
- package/dist/tools/definitions/system.d.ts +0 -1
- package/dist/tools/definitions/system.js +32 -12
- package/dist/tools/definitions/video.d.ts +0 -1
- package/dist/tools/definitions/video.js +6 -3
- package/dist/tools/definitions.d.ts +0 -1
- package/dist/tools/definitions.js +0 -1
- package/dist/tools/handlers/ask-question.d.ts +0 -1
- package/dist/tools/handlers/ask-question.js +47 -18
- package/dist/tools/handlers/audio-video.d.ts +0 -1
- package/dist/tools/handlers/audio-video.js +0 -1
- package/dist/tools/handlers/auth.d.ts +0 -1
- package/dist/tools/handlers/auth.js +0 -1
- package/dist/tools/handlers/error-utils.d.ts +0 -1
- package/dist/tools/handlers/error-utils.js +0 -1
- package/dist/tools/handlers/gemini.d.ts +0 -1
- package/dist/tools/handlers/gemini.js +0 -1
- package/dist/tools/handlers/index.d.ts +0 -1
- package/dist/tools/handlers/index.js +0 -1
- package/dist/tools/handlers/notebook-creation.d.ts +0 -1
- package/dist/tools/handlers/notebook-creation.js +16 -1
- package/dist/tools/handlers/notebook-management.d.ts +0 -1
- package/dist/tools/handlers/notebook-management.js +7 -2
- package/dist/tools/handlers/session-management.d.ts +0 -1
- package/dist/tools/handlers/session-management.js +0 -1
- package/dist/tools/handlers/system.d.ts +0 -1
- package/dist/tools/handlers/system.js +0 -1
- package/dist/tools/handlers/types.d.ts +0 -1
- package/dist/tools/handlers/types.js +0 -1
- package/dist/tools/handlers/webhooks.d.ts +0 -1
- package/dist/tools/handlers/webhooks.js +0 -1
- package/dist/tools/icons.d.ts +0 -1
- package/dist/tools/icons.js +0 -1
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.js +0 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.js +0 -1
- package/dist/utils/audit-logger.d.ts +11 -1
- package/dist/utils/audit-logger.js +189 -21
- package/dist/utils/cleanup-manager.d.ts +0 -1
- package/dist/utils/cleanup-manager.js +0 -1
- package/dist/utils/cli-handler.d.ts +0 -1
- package/dist/utils/cli-handler.js +0 -1
- package/dist/utils/crypto.d.ts +18 -9
- package/dist/utils/crypto.js +93 -28
- package/dist/utils/file-lock.d.ts +15 -1
- package/dist/utils/file-lock.js +67 -59
- package/dist/utils/file-permissions.d.ts +0 -1
- package/dist/utils/file-permissions.js +35 -7
- package/dist/utils/logger.d.ts +0 -1
- package/dist/utils/logger.js +0 -1
- package/dist/utils/page-utils.d.ts +0 -1
- package/dist/utils/page-utils.js +32 -28
- package/dist/utils/response-validator.d.ts +0 -1
- package/dist/utils/response-validator.js +18 -15
- package/dist/utils/secrets-scanner.d.ts +0 -1
- package/dist/utils/secrets-scanner.js +32 -7
- package/dist/utils/secure-memory.d.ts +34 -16
- package/dist/utils/secure-memory.js +40 -25
- package/dist/utils/security.d.ts +0 -1
- package/dist/utils/security.js +66 -39
- package/dist/utils/settings-manager.d.ts +9 -1
- package/dist/utils/settings-manager.js +45 -2
- package/dist/utils/stealth-utils.d.ts +0 -1
- package/dist/utils/stealth-utils.js +11 -9
- package/dist/webhooks/index.d.ts +0 -1
- package/dist/webhooks/index.js +0 -1
- package/dist/webhooks/types.d.ts +0 -1
- package/dist/webhooks/types.js +0 -1
- package/dist/webhooks/webhook-dispatcher.d.ts +0 -1
- package/dist/webhooks/webhook-dispatcher.js +0 -1
- package/package.json +5 -4
- package/dist/auth/auth-manager.d.ts.map +0 -1
- package/dist/auth/auth-manager.js.map +0 -1
- package/dist/auth/mcp-auth.d.ts.map +0 -1
- package/dist/auth/mcp-auth.js.map +0 -1
- package/dist/compliance/alert-manager.d.ts.map +0 -1
- package/dist/compliance/alert-manager.js.map +0 -1
- package/dist/compliance/breach-detection.d.ts.map +0 -1
- package/dist/compliance/breach-detection.js.map +0 -1
- package/dist/compliance/change-log.d.ts.map +0 -1
- package/dist/compliance/change-log.js.map +0 -1
- package/dist/compliance/compliance-logger.d.ts.map +0 -1
- package/dist/compliance/compliance-logger.js.map +0 -1
- package/dist/compliance/compliance-tools.d.ts.map +0 -1
- package/dist/compliance/compliance-tools.js.map +0 -1
- package/dist/compliance/consent-manager.d.ts.map +0 -1
- package/dist/compliance/consent-manager.js.map +0 -1
- package/dist/compliance/dashboard.d.ts.map +0 -1
- package/dist/compliance/dashboard.js.map +0 -1
- package/dist/compliance/data-classification.d.ts.map +0 -1
- package/dist/compliance/data-classification.js.map +0 -1
- package/dist/compliance/data-erasure.d.ts.map +0 -1
- package/dist/compliance/data-erasure.js.map +0 -1
- package/dist/compliance/data-export.d.ts.map +0 -1
- package/dist/compliance/data-export.js.map +0 -1
- package/dist/compliance/data-inventory.d.ts.map +0 -1
- package/dist/compliance/data-inventory.js.map +0 -1
- package/dist/compliance/dsar-handler.d.ts.map +0 -1
- package/dist/compliance/dsar-handler.js.map +0 -1
- package/dist/compliance/evidence-collector.d.ts.map +0 -1
- package/dist/compliance/evidence-collector.js.map +0 -1
- package/dist/compliance/health-monitor.d.ts.map +0 -1
- package/dist/compliance/health-monitor.js.map +0 -1
- package/dist/compliance/incident-manager.d.ts.map +0 -1
- package/dist/compliance/incident-manager.js.map +0 -1
- package/dist/compliance/index.d.ts.map +0 -1
- package/dist/compliance/index.js.map +0 -1
- package/dist/compliance/policy-docs.d.ts.map +0 -1
- package/dist/compliance/policy-docs.js.map +0 -1
- package/dist/compliance/privacy-notice-text.d.ts.map +0 -1
- package/dist/compliance/privacy-notice-text.js.map +0 -1
- package/dist/compliance/privacy-notice.d.ts.map +0 -1
- package/dist/compliance/privacy-notice.js.map +0 -1
- package/dist/compliance/report-generator.d.ts.map +0 -1
- package/dist/compliance/report-generator.js.map +0 -1
- package/dist/compliance/retention-engine.d.ts.map +0 -1
- package/dist/compliance/retention-engine.js.map +0 -1
- package/dist/compliance/siem-exporter.d.ts.map +0 -1
- package/dist/compliance/siem-exporter.js.map +0 -1
- package/dist/compliance/types.d.ts.map +0 -1
- package/dist/compliance/types.js.map +0 -1
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/events/event-emitter.d.ts.map +0 -1
- package/dist/events/event-emitter.js.map +0 -1
- package/dist/events/event-types.d.ts.map +0 -1
- package/dist/events/event-types.js.map +0 -1
- package/dist/gemini/gemini-client.d.ts.map +0 -1
- package/dist/gemini/gemini-client.js.map +0 -1
- package/dist/gemini/index.d.ts.map +0 -1
- package/dist/gemini/index.js.map +0 -1
- package/dist/gemini/pdf-chunker.d.ts.map +0 -1
- package/dist/gemini/pdf-chunker.js.map +0 -1
- package/dist/gemini/types.d.ts.map +0 -1
- package/dist/gemini/types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/library/notebook-library.d.ts.map +0 -1
- package/dist/library/notebook-library.js.map +0 -1
- package/dist/library/types.d.ts.map +0 -1
- package/dist/library/types.js.map +0 -1
- package/dist/logging/index.d.ts.map +0 -1
- package/dist/logging/index.js.map +0 -1
- package/dist/logging/query-logger.d.ts.map +0 -1
- package/dist/logging/query-logger.js.map +0 -1
- package/dist/notebook-creation/audio-manager.d.ts.map +0 -1
- package/dist/notebook-creation/audio-manager.js.map +0 -1
- package/dist/notebook-creation/browser-options.d.ts.map +0 -1
- package/dist/notebook-creation/browser-options.js.map +0 -1
- package/dist/notebook-creation/data-table-manager.d.ts.map +0 -1
- package/dist/notebook-creation/data-table-manager.js.map +0 -1
- package/dist/notebook-creation/discover-creation-flow.d.ts +0 -2
- package/dist/notebook-creation/discover-creation-flow.d.ts.map +0 -1
- package/dist/notebook-creation/discover-creation-flow.js +0 -177
- package/dist/notebook-creation/discover-creation-flow.js.map +0 -1
- package/dist/notebook-creation/discover-quota.d.ts +0 -2
- package/dist/notebook-creation/discover-quota.d.ts.map +0 -1
- package/dist/notebook-creation/discover-quota.js +0 -194
- package/dist/notebook-creation/discover-quota.js.map +0 -1
- package/dist/notebook-creation/discover-source-dialog.d.ts +0 -8
- package/dist/notebook-creation/discover-source-dialog.d.ts.map +0 -1
- package/dist/notebook-creation/discover-source-dialog.js +0 -134
- package/dist/notebook-creation/discover-source-dialog.js.map +0 -1
- package/dist/notebook-creation/discover-sources.d.ts +0 -8
- package/dist/notebook-creation/discover-sources.d.ts.map +0 -1
- package/dist/notebook-creation/discover-sources.js +0 -272
- package/dist/notebook-creation/discover-sources.js.map +0 -1
- package/dist/notebook-creation/discover-text-input.d.ts +0 -7
- package/dist/notebook-creation/discover-text-input.d.ts.map +0 -1
- package/dist/notebook-creation/discover-text-input.js +0 -135
- package/dist/notebook-creation/discover-text-input.js.map +0 -1
- package/dist/notebook-creation/dom-scripts.d.ts.map +0 -1
- package/dist/notebook-creation/dom-scripts.js.map +0 -1
- package/dist/notebook-creation/errors.d.ts.map +0 -1
- package/dist/notebook-creation/errors.js.map +0 -1
- package/dist/notebook-creation/index.d.ts.map +0 -1
- package/dist/notebook-creation/index.js.map +0 -1
- package/dist/notebook-creation/notebook-creator.d.ts.map +0 -1
- package/dist/notebook-creation/notebook-creator.js.map +0 -1
- package/dist/notebook-creation/notebook-nav.d.ts.map +0 -1
- package/dist/notebook-creation/notebook-nav.js.map +0 -1
- package/dist/notebook-creation/notebook-sync.d.ts.map +0 -1
- package/dist/notebook-creation/notebook-sync.js.map +0 -1
- package/dist/notebook-creation/run-discovery.d.ts +0 -11
- package/dist/notebook-creation/run-discovery.d.ts.map +0 -1
- package/dist/notebook-creation/run-discovery.js +0 -151
- package/dist/notebook-creation/run-discovery.js.map +0 -1
- package/dist/notebook-creation/selector-discovery.d.ts +0 -65
- package/dist/notebook-creation/selector-discovery.d.ts.map +0 -1
- package/dist/notebook-creation/selector-discovery.js +0 -414
- package/dist/notebook-creation/selector-discovery.js.map +0 -1
- package/dist/notebook-creation/selectors.d.ts.map +0 -1
- package/dist/notebook-creation/selectors.js.map +0 -1
- package/dist/notebook-creation/selectors.ts +0 -112
- package/dist/notebook-creation/source-manager.d.ts.map +0 -1
- package/dist/notebook-creation/source-manager.js.map +0 -1
- package/dist/notebook-creation/test-create.d.ts +0 -8
- package/dist/notebook-creation/test-create.d.ts.map +0 -1
- package/dist/notebook-creation/test-create.js +0 -72
- package/dist/notebook-creation/test-create.js.map +0 -1
- package/dist/notebook-creation/types.d.ts.map +0 -1
- package/dist/notebook-creation/types.js.map +0 -1
- package/dist/notebook-creation/video-manager.d.ts.map +0 -1
- package/dist/notebook-creation/video-manager.js.map +0 -1
- package/dist/observability/metrics.d.ts.map +0 -1
- package/dist/observability/metrics.js.map +0 -1
- package/dist/quota/index.d.ts.map +0 -1
- package/dist/quota/index.js.map +0 -1
- package/dist/quota/quota-manager.d.ts.map +0 -1
- package/dist/quota/quota-manager.js.map +0 -1
- package/dist/resources/resource-handlers.d.ts.map +0 -1
- package/dist/resources/resource-handlers.js.map +0 -1
- package/dist/session/browser-session.d.ts.map +0 -1
- package/dist/session/browser-session.js.map +0 -1
- package/dist/session/session-manager.d.ts.map +0 -1
- package/dist/session/session-manager.js.map +0 -1
- package/dist/session/session-timeout.d.ts.map +0 -1
- package/dist/session/session-timeout.js.map +0 -1
- package/dist/session/shared-context-manager.d.ts.map +0 -1
- package/dist/session/shared-context-manager.js.map +0 -1
- package/dist/tools/annotations.d.ts.map +0 -1
- package/dist/tools/annotations.js.map +0 -1
- package/dist/tools/definitions/ask-question.d.ts.map +0 -1
- package/dist/tools/definitions/ask-question.js.map +0 -1
- package/dist/tools/definitions/chat-history.d.ts.map +0 -1
- package/dist/tools/definitions/chat-history.js.map +0 -1
- package/dist/tools/definitions/data-tables.d.ts.map +0 -1
- package/dist/tools/definitions/data-tables.js.map +0 -1
- package/dist/tools/definitions/gemini.d.ts.map +0 -1
- package/dist/tools/definitions/gemini.js.map +0 -1
- package/dist/tools/definitions/notebook-management.d.ts.map +0 -1
- package/dist/tools/definitions/notebook-management.js.map +0 -1
- package/dist/tools/definitions/query-history.d.ts.map +0 -1
- package/dist/tools/definitions/query-history.js.map +0 -1
- package/dist/tools/definitions/session-management.d.ts.map +0 -1
- package/dist/tools/definitions/session-management.js.map +0 -1
- package/dist/tools/definitions/system.d.ts.map +0 -1
- package/dist/tools/definitions/system.js.map +0 -1
- package/dist/tools/definitions/video.d.ts.map +0 -1
- package/dist/tools/definitions/video.js.map +0 -1
- package/dist/tools/definitions.d.ts.map +0 -1
- package/dist/tools/definitions.js.map +0 -1
- package/dist/tools/handlers/ask-question.d.ts.map +0 -1
- package/dist/tools/handlers/ask-question.js.map +0 -1
- package/dist/tools/handlers/audio-video.d.ts.map +0 -1
- package/dist/tools/handlers/audio-video.js.map +0 -1
- package/dist/tools/handlers/auth.d.ts.map +0 -1
- package/dist/tools/handlers/auth.js.map +0 -1
- package/dist/tools/handlers/error-utils.d.ts.map +0 -1
- package/dist/tools/handlers/error-utils.js.map +0 -1
- package/dist/tools/handlers/gemini.d.ts.map +0 -1
- package/dist/tools/handlers/gemini.js.map +0 -1
- package/dist/tools/handlers/index.d.ts.map +0 -1
- package/dist/tools/handlers/index.js.map +0 -1
- package/dist/tools/handlers/notebook-creation.d.ts.map +0 -1
- package/dist/tools/handlers/notebook-creation.js.map +0 -1
- package/dist/tools/handlers/notebook-management.d.ts.map +0 -1
- package/dist/tools/handlers/notebook-management.js.map +0 -1
- package/dist/tools/handlers/session-management.d.ts.map +0 -1
- package/dist/tools/handlers/session-management.js.map +0 -1
- package/dist/tools/handlers/system.d.ts.map +0 -1
- package/dist/tools/handlers/system.js.map +0 -1
- package/dist/tools/handlers/types.d.ts.map +0 -1
- package/dist/tools/handlers/types.js.map +0 -1
- package/dist/tools/handlers/webhooks.d.ts.map +0 -1
- package/dist/tools/handlers/webhooks.js.map +0 -1
- package/dist/tools/handlers.d.ts +0 -666
- package/dist/tools/handlers.d.ts.map +0 -1
- package/dist/tools/handlers.js +0 -2929
- package/dist/tools/handlers.js.map +0 -1
- package/dist/tools/icons.d.ts.map +0 -1
- package/dist/tools/icons.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils/audit-logger.d.ts.map +0 -1
- package/dist/utils/audit-logger.js.map +0 -1
- package/dist/utils/cert-pinning.d.ts +0 -97
- package/dist/utils/cert-pinning.d.ts.map +0 -1
- package/dist/utils/cert-pinning.js +0 -328
- package/dist/utils/cert-pinning.js.map +0 -1
- package/dist/utils/cleanup-manager.d.ts.map +0 -1
- package/dist/utils/cleanup-manager.js.map +0 -1
- package/dist/utils/cli-handler.d.ts.map +0 -1
- package/dist/utils/cli-handler.js.map +0 -1
- package/dist/utils/crypto.d.ts.map +0 -1
- package/dist/utils/crypto.js.map +0 -1
- package/dist/utils/file-lock.d.ts.map +0 -1
- package/dist/utils/file-lock.js.map +0 -1
- package/dist/utils/file-permissions.d.ts.map +0 -1
- package/dist/utils/file-permissions.js.map +0 -1
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/page-utils.d.ts.map +0 -1
- package/dist/utils/page-utils.js.map +0 -1
- package/dist/utils/response-validator.d.ts.map +0 -1
- package/dist/utils/response-validator.js.map +0 -1
- package/dist/utils/secrets-scanner.d.ts.map +0 -1
- package/dist/utils/secrets-scanner.js.map +0 -1
- package/dist/utils/secure-memory.d.ts.map +0 -1
- package/dist/utils/secure-memory.js.map +0 -1
- package/dist/utils/security.d.ts.map +0 -1
- package/dist/utils/security.js.map +0 -1
- package/dist/utils/settings-manager.d.ts.map +0 -1
- package/dist/utils/settings-manager.js.map +0 -1
- package/dist/utils/stealth-utils.d.ts.map +0 -1
- package/dist/utils/stealth-utils.js.map +0 -1
- package/dist/utils/tool-validation.d.ts +0 -93
- package/dist/utils/tool-validation.d.ts.map +0 -1
- package/dist/utils/tool-validation.js +0 -277
- package/dist/utils/tool-validation.js.map +0 -1
- package/dist/webhooks/index.d.ts.map +0 -1
- package/dist/webhooks/index.js.map +0 -1
- package/dist/webhooks/types.d.ts.map +0 -1
- package/dist/webhooks/types.js.map +0 -1
- package/dist/webhooks/webhook-dispatcher.d.ts.map +0 -1
- package/dist/webhooks/webhook-dispatcher.js.map +0 -1
- package/docs/COMPLIANCE-SPEC.md +0 -1452
- package/docs/MCP-DIRECTORY-LISTINGS.md +0 -91
- package/docs/SECURITY-FORK-OPPORTUNITIES.md +0 -79
- package/docs/SECURITY_IMPLEMENTATION_PLAN.md +0 -437
- package/docs/archive/ISSUES-legacy-2026-04-24.md +0 -644
- package/docs/configuration.md +0 -94
- package/docs/dependency-risk.md +0 -25
- package/docs/improvement-sprint-2026.2.10.md +0 -210
- package/docs/testing-runbook.md +0 -166
- package/docs/tools.md +0 -34
- package/docs/troubleshooting.md +0 -59
- package/docs/usage-guide.md +0 -246
package/dist/tools/handlers.js
DELETED
|
@@ -1,2929 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Tool Handlers
|
|
3
|
-
*
|
|
4
|
-
* Implements the logic for all MCP tools.
|
|
5
|
-
*/
|
|
6
|
-
import { CONFIG, applyBrowserOptions } from "../config.js";
|
|
7
|
-
import { log } from "../utils/logger.js";
|
|
8
|
-
import { RateLimitError } from "../errors.js";
|
|
9
|
-
import { validateNotebookUrl, validateNotebookId, validateSessionId, validateQuestion, sanitizeForLogging, RateLimiter, SecurityError, } from "../utils/security.js";
|
|
10
|
-
import { audit } from "../utils/audit-logger.js";
|
|
11
|
-
import { validateResponse } from "../utils/response-validator.js";
|
|
12
|
-
import { CleanupManager } from "../utils/cleanup-manager.js";
|
|
13
|
-
import { NotebookCreator } from "../notebook-creation/notebook-creator.js";
|
|
14
|
-
import { NotebookSync } from "../notebook-creation/notebook-sync.js";
|
|
15
|
-
import { SourceManager } from "../notebook-creation/source-manager.js";
|
|
16
|
-
import { AudioManager } from "../notebook-creation/audio-manager.js";
|
|
17
|
-
import { VideoManager } from "../notebook-creation/video-manager.js";
|
|
18
|
-
import { DataTableManager } from "../notebook-creation/data-table-manager.js";
|
|
19
|
-
import { getWebhookDispatcher } from "../webhooks/index.js";
|
|
20
|
-
import { getQuotaManager } from "../quota/index.js";
|
|
21
|
-
import { getQueryLogger } from "../logging/index.js";
|
|
22
|
-
import { GeminiClient, } from "../gemini/index.js";
|
|
23
|
-
const FOLLOW_UP_REMINDER = "\n\nEXTREMELY IMPORTANT: Is that ALL you need to know? You can always ask another question using the same session ID! Think about it carefully: before you reply to the user, review their original request and this answer. If anything is still unclear or missing, ask me another question first.";
|
|
24
|
-
/**
|
|
25
|
-
* MCP Tool Handlers
|
|
26
|
-
*/
|
|
27
|
-
export class ToolHandlers {
|
|
28
|
-
sessionManager;
|
|
29
|
-
authManager;
|
|
30
|
-
library;
|
|
31
|
-
rateLimiter;
|
|
32
|
-
geminiClient;
|
|
33
|
-
constructor(sessionManager, authManager, library) {
|
|
34
|
-
this.sessionManager = sessionManager;
|
|
35
|
-
this.authManager = authManager;
|
|
36
|
-
this.library = library;
|
|
37
|
-
// Rate limit: 100 requests per minute per session (protective limit)
|
|
38
|
-
this.rateLimiter = new RateLimiter(100, 60000);
|
|
39
|
-
// Initialize Gemini client (may be unavailable if no API key)
|
|
40
|
-
this.geminiClient = new GeminiClient();
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Handle ask_question tool
|
|
44
|
-
*/
|
|
45
|
-
async handleAskQuestion(args, sendProgress) {
|
|
46
|
-
const { show_browser, browser_options } = args;
|
|
47
|
-
const startTime = Date.now();
|
|
48
|
-
log.info(`🔧 [TOOL] ask_question called`);
|
|
49
|
-
// === SECURITY: Input validation ===
|
|
50
|
-
let safeQuestion;
|
|
51
|
-
let safeSessionId;
|
|
52
|
-
let safeNotebookId;
|
|
53
|
-
let safeNotebookUrl;
|
|
54
|
-
try {
|
|
55
|
-
// Validate question (required)
|
|
56
|
-
safeQuestion = validateQuestion(args.question);
|
|
57
|
-
log.info(` Question: "${sanitizeForLogging(safeQuestion.substring(0, 100))}"...`);
|
|
58
|
-
// Validate optional session_id
|
|
59
|
-
if (args.session_id) {
|
|
60
|
-
safeSessionId = validateSessionId(args.session_id);
|
|
61
|
-
log.info(` Session ID: ${safeSessionId}`);
|
|
62
|
-
}
|
|
63
|
-
// Validate optional notebook_id
|
|
64
|
-
if (args.notebook_id) {
|
|
65
|
-
safeNotebookId = validateNotebookId(args.notebook_id);
|
|
66
|
-
log.info(` Notebook ID: ${safeNotebookId}`);
|
|
67
|
-
}
|
|
68
|
-
// Validate optional notebook_url (CRITICAL - prevents URL injection)
|
|
69
|
-
if (args.notebook_url) {
|
|
70
|
-
safeNotebookUrl = validateNotebookUrl(args.notebook_url);
|
|
71
|
-
log.info(` Notebook URL: ${safeNotebookUrl}`);
|
|
72
|
-
}
|
|
73
|
-
// Rate limiting check
|
|
74
|
-
const rateLimitKey = safeSessionId || 'global';
|
|
75
|
-
if (!this.rateLimiter.isAllowed(rateLimitKey)) {
|
|
76
|
-
log.warning(`🚫 Rate limit exceeded for ${rateLimitKey}`);
|
|
77
|
-
await audit.security("rate_limit_exceeded", "warning", {
|
|
78
|
-
session_id: rateLimitKey,
|
|
79
|
-
remaining: this.rateLimiter.getRemaining(rateLimitKey),
|
|
80
|
-
});
|
|
81
|
-
await audit.tool("ask_question", args, false, Date.now() - startTime, "Rate limit exceeded");
|
|
82
|
-
return {
|
|
83
|
-
success: false,
|
|
84
|
-
error: `Rate limit exceeded. Please wait before making more requests. Remaining: ${this.rateLimiter.getRemaining(rateLimitKey)}`,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
// === QUOTA CHECK ===
|
|
88
|
-
const quotaManager = getQuotaManager();
|
|
89
|
-
const canQuery = quotaManager.canMakeQuery();
|
|
90
|
-
if (!canQuery.allowed) {
|
|
91
|
-
log.warning(`⚠️ Quota limit: ${canQuery.reason}`);
|
|
92
|
-
await audit.tool("ask_question", args, false, Date.now() - startTime, canQuery.reason || "Query quota exceeded");
|
|
93
|
-
return {
|
|
94
|
-
success: false,
|
|
95
|
-
error: canQuery.reason || "Daily query limit reached. Try again tomorrow or upgrade your plan.",
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
if (error instanceof SecurityError) {
|
|
101
|
-
log.error(`🛡️ [SECURITY] Validation failed: ${error.message}`);
|
|
102
|
-
await audit.security("validation_failed", "error", {
|
|
103
|
-
tool: "ask_question",
|
|
104
|
-
error: error.message,
|
|
105
|
-
});
|
|
106
|
-
await audit.tool("ask_question", args, false, Date.now() - startTime, error.message);
|
|
107
|
-
return {
|
|
108
|
-
success: false,
|
|
109
|
-
error: `Security validation failed: ${error.message}`,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
// Resolve notebook URL (using validated values)
|
|
116
|
-
let resolvedNotebookUrl = safeNotebookUrl;
|
|
117
|
-
if (!resolvedNotebookUrl && safeNotebookId) {
|
|
118
|
-
const notebook = this.library.incrementUseCount(safeNotebookId);
|
|
119
|
-
if (!notebook) {
|
|
120
|
-
throw new Error(`Notebook not found in library: ${safeNotebookId}`);
|
|
121
|
-
}
|
|
122
|
-
resolvedNotebookUrl = notebook.url;
|
|
123
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
124
|
-
}
|
|
125
|
-
else if (!resolvedNotebookUrl) {
|
|
126
|
-
const active = this.library.getActiveNotebook();
|
|
127
|
-
if (active) {
|
|
128
|
-
const notebook = this.library.incrementUseCount(active.id);
|
|
129
|
-
if (!notebook) {
|
|
130
|
-
throw new Error(`Active notebook not found: ${active.id}`);
|
|
131
|
-
}
|
|
132
|
-
resolvedNotebookUrl = notebook.url;
|
|
133
|
-
log.info(` Using active notebook: ${notebook.name}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Progress: Getting or creating session
|
|
137
|
-
await sendProgress?.("Getting or creating browser session...", 1, 5);
|
|
138
|
-
// Calculate overrideHeadless parameter for session manager
|
|
139
|
-
// show_browser takes precedence over browser_options.headless
|
|
140
|
-
let overrideHeadless = undefined;
|
|
141
|
-
if (show_browser !== undefined) {
|
|
142
|
-
overrideHeadless = show_browser;
|
|
143
|
-
}
|
|
144
|
-
else if (browser_options?.show !== undefined) {
|
|
145
|
-
overrideHeadless = browser_options.show;
|
|
146
|
-
}
|
|
147
|
-
else if (browser_options?.headless !== undefined) {
|
|
148
|
-
overrideHeadless = !browser_options.headless;
|
|
149
|
-
}
|
|
150
|
-
// Get or create session (with headless override to handle mode changes)
|
|
151
|
-
const session = await this.sessionManager.getOrCreateSession(safeSessionId, resolvedNotebookUrl, overrideHeadless);
|
|
152
|
-
// Progress: Asking question
|
|
153
|
-
await sendProgress?.("Asking question to NotebookLM...", 2, 5);
|
|
154
|
-
// Ask the question (pass progress callback) - using validated question
|
|
155
|
-
const rawAnswer = await session.ask(safeQuestion, sendProgress);
|
|
156
|
-
// === SECURITY: Validate response for prompt injection & malicious content ===
|
|
157
|
-
await sendProgress?.("Validating response security...", 4, 5);
|
|
158
|
-
const validationResult = await validateResponse(rawAnswer);
|
|
159
|
-
// Use sanitized response if issues were found
|
|
160
|
-
let finalAnswer;
|
|
161
|
-
let securityWarnings = [];
|
|
162
|
-
if (!validationResult.safe) {
|
|
163
|
-
log.warning(`🛡️ Response contained blocked content, using sanitized version`);
|
|
164
|
-
finalAnswer = validationResult.sanitized;
|
|
165
|
-
securityWarnings = validationResult.blocked;
|
|
166
|
-
}
|
|
167
|
-
else if (validationResult.warnings.length > 0) {
|
|
168
|
-
log.info(`⚠️ Response had ${validationResult.warnings.length} warnings`);
|
|
169
|
-
finalAnswer = rawAnswer;
|
|
170
|
-
securityWarnings = validationResult.warnings;
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
finalAnswer = rawAnswer;
|
|
174
|
-
}
|
|
175
|
-
const answer = `${finalAnswer.trimEnd()}${FOLLOW_UP_REMINDER}`;
|
|
176
|
-
// Get session info
|
|
177
|
-
const sessionInfo = session.getInfo();
|
|
178
|
-
// Get quota status for response visibility
|
|
179
|
-
const quotaStatus = getQuotaManager().getDetailedStatus();
|
|
180
|
-
const result = {
|
|
181
|
-
status: "success",
|
|
182
|
-
question: safeQuestion,
|
|
183
|
-
answer,
|
|
184
|
-
session_id: session.sessionId,
|
|
185
|
-
notebook_url: session.notebookUrl,
|
|
186
|
-
session_info: {
|
|
187
|
-
age_seconds: sessionInfo.age_seconds,
|
|
188
|
-
message_count: sessionInfo.message_count,
|
|
189
|
-
last_activity: sessionInfo.last_activity,
|
|
190
|
-
},
|
|
191
|
-
// Include quota info for visibility
|
|
192
|
-
quota_info: {
|
|
193
|
-
queries_remaining: quotaStatus.queries.remaining,
|
|
194
|
-
queries_used_today: quotaStatus.queries.used,
|
|
195
|
-
queries_limit: quotaStatus.queries.limit,
|
|
196
|
-
should_stop: quotaStatus.queries.shouldStop,
|
|
197
|
-
tier: quotaStatus.tier,
|
|
198
|
-
warnings: quotaStatus.warnings,
|
|
199
|
-
},
|
|
200
|
-
// Include security warnings if any
|
|
201
|
-
...(securityWarnings.length > 0 && { security_warnings: securityWarnings }),
|
|
202
|
-
};
|
|
203
|
-
// Progress: Complete
|
|
204
|
-
await sendProgress?.("Question answered successfully!", 5, 5);
|
|
205
|
-
log.success(`✅ [TOOL] ask_question completed successfully`);
|
|
206
|
-
// Update quota tracking (atomic for concurrent session safety)
|
|
207
|
-
await getQuotaManager().incrementQueryCountAtomic();
|
|
208
|
-
// Log query for research history (Phase 1)
|
|
209
|
-
const queryLogger = getQueryLogger();
|
|
210
|
-
const resolvedNotebook = safeNotebookId ? this.library.getNotebook(safeNotebookId) : null;
|
|
211
|
-
await queryLogger.logQuery({
|
|
212
|
-
sessionId: session.sessionId,
|
|
213
|
-
notebookId: safeNotebookId,
|
|
214
|
-
notebookUrl: session.notebookUrl,
|
|
215
|
-
notebookName: resolvedNotebook?.name,
|
|
216
|
-
question: safeQuestion,
|
|
217
|
-
answer: finalAnswer,
|
|
218
|
-
answerLength: finalAnswer.length,
|
|
219
|
-
durationMs: Date.now() - startTime,
|
|
220
|
-
quotaInfo: {
|
|
221
|
-
used: quotaStatus.queries.used + 1, // +1 because we just incremented
|
|
222
|
-
limit: quotaStatus.queries.limit,
|
|
223
|
-
remaining: quotaStatus.queries.remaining - 1,
|
|
224
|
-
tier: quotaStatus.tier,
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
// Audit: successful tool call
|
|
228
|
-
await audit.tool("ask_question", {
|
|
229
|
-
question_length: safeQuestion.length,
|
|
230
|
-
session_id: safeSessionId,
|
|
231
|
-
notebook_id: safeNotebookId,
|
|
232
|
-
}, true, Date.now() - startTime);
|
|
233
|
-
return {
|
|
234
|
-
success: true,
|
|
235
|
-
data: result,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
240
|
-
// Special handling for rate limit errors
|
|
241
|
-
if (error instanceof RateLimitError || errorMessage.toLowerCase().includes("rate limit")) {
|
|
242
|
-
log.error(`🚫 [TOOL] Rate limit detected`);
|
|
243
|
-
await audit.security("notebooklm_rate_limit", "warning", {
|
|
244
|
-
session_id: safeSessionId,
|
|
245
|
-
});
|
|
246
|
-
await audit.tool("ask_question", args, false, Date.now() - startTime, "NotebookLM rate limit");
|
|
247
|
-
return {
|
|
248
|
-
success: false,
|
|
249
|
-
error: "NotebookLM rate limit reached (50 queries/day for free accounts).\n\n" +
|
|
250
|
-
"You can:\n" +
|
|
251
|
-
"1. Use the 're_auth' tool to login with a different Google account\n" +
|
|
252
|
-
"2. Wait until tomorrow for the quota to reset\n" +
|
|
253
|
-
"3. Upgrade to Google AI Pro/Ultra for 5x higher limits\n\n" +
|
|
254
|
-
`Original error: ${errorMessage}`,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
log.error(`❌ [TOOL] ask_question failed: ${errorMessage}`);
|
|
258
|
-
await audit.tool("ask_question", args, false, Date.now() - startTime, errorMessage);
|
|
259
|
-
return {
|
|
260
|
-
success: false,
|
|
261
|
-
error: errorMessage,
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Handle list_sessions tool
|
|
267
|
-
*/
|
|
268
|
-
async handleListSessions() {
|
|
269
|
-
log.info(`🔧 [TOOL] list_sessions called`);
|
|
270
|
-
try {
|
|
271
|
-
const stats = this.sessionManager.getStats();
|
|
272
|
-
const sessions = this.sessionManager.getAllSessionsInfo();
|
|
273
|
-
const result = {
|
|
274
|
-
active_sessions: stats.active_sessions,
|
|
275
|
-
max_sessions: stats.max_sessions,
|
|
276
|
-
session_timeout: stats.session_timeout,
|
|
277
|
-
oldest_session_seconds: stats.oldest_session_seconds,
|
|
278
|
-
total_messages: stats.total_messages,
|
|
279
|
-
sessions: sessions.map((info) => ({
|
|
280
|
-
id: info.id,
|
|
281
|
-
created_at: info.created_at,
|
|
282
|
-
last_activity: info.last_activity,
|
|
283
|
-
age_seconds: info.age_seconds,
|
|
284
|
-
inactive_seconds: info.inactive_seconds,
|
|
285
|
-
message_count: info.message_count,
|
|
286
|
-
notebook_url: info.notebook_url,
|
|
287
|
-
})),
|
|
288
|
-
};
|
|
289
|
-
log.success(`✅ [TOOL] list_sessions completed (${result.active_sessions} sessions)`);
|
|
290
|
-
return {
|
|
291
|
-
success: true,
|
|
292
|
-
data: result,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
297
|
-
log.error(`❌ [TOOL] list_sessions failed: ${errorMessage}`);
|
|
298
|
-
return {
|
|
299
|
-
success: false,
|
|
300
|
-
error: errorMessage,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Handle close_session tool
|
|
306
|
-
*/
|
|
307
|
-
async handleCloseSession(args) {
|
|
308
|
-
const { session_id } = args;
|
|
309
|
-
log.info(`🔧 [TOOL] close_session called`);
|
|
310
|
-
log.info(` Session ID: ${session_id}`);
|
|
311
|
-
try {
|
|
312
|
-
const closed = await this.sessionManager.closeSession(session_id);
|
|
313
|
-
if (closed) {
|
|
314
|
-
log.success(`✅ [TOOL] close_session completed`);
|
|
315
|
-
return {
|
|
316
|
-
success: true,
|
|
317
|
-
data: {
|
|
318
|
-
status: "success",
|
|
319
|
-
message: `Session ${session_id} closed successfully`,
|
|
320
|
-
session_id,
|
|
321
|
-
},
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
log.warning(`⚠️ [TOOL] Session ${session_id} not found`);
|
|
326
|
-
return {
|
|
327
|
-
success: false,
|
|
328
|
-
error: `Session ${session_id} not found`,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
catch (error) {
|
|
333
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
334
|
-
log.error(`❌ [TOOL] close_session failed: ${errorMessage}`);
|
|
335
|
-
return {
|
|
336
|
-
success: false,
|
|
337
|
-
error: errorMessage,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
/**
|
|
342
|
-
* Handle reset_session tool
|
|
343
|
-
*/
|
|
344
|
-
async handleResetSession(args) {
|
|
345
|
-
const { session_id } = args;
|
|
346
|
-
log.info(`🔧 [TOOL] reset_session called`);
|
|
347
|
-
log.info(` Session ID: ${session_id}`);
|
|
348
|
-
try {
|
|
349
|
-
const session = this.sessionManager.getSession(session_id);
|
|
350
|
-
if (!session) {
|
|
351
|
-
log.warning(`⚠️ [TOOL] Session ${session_id} not found`);
|
|
352
|
-
return {
|
|
353
|
-
success: false,
|
|
354
|
-
error: `Session ${session_id} not found`,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
await session.reset();
|
|
358
|
-
log.success(`✅ [TOOL] reset_session completed`);
|
|
359
|
-
return {
|
|
360
|
-
success: true,
|
|
361
|
-
data: {
|
|
362
|
-
status: "success",
|
|
363
|
-
message: `Session ${session_id} reset successfully`,
|
|
364
|
-
session_id,
|
|
365
|
-
},
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
370
|
-
log.error(`❌ [TOOL] reset_session failed: ${errorMessage}`);
|
|
371
|
-
return {
|
|
372
|
-
success: false,
|
|
373
|
-
error: errorMessage,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Handle get_health tool
|
|
379
|
-
*/
|
|
380
|
-
async handleGetHealth(args) {
|
|
381
|
-
log.info(`🔧 [TOOL] get_health called${args?.deep_check ? ' (deep check)' : ''}`);
|
|
382
|
-
try {
|
|
383
|
-
// Check authentication status
|
|
384
|
-
const statePath = await this.authManager.getValidStatePath();
|
|
385
|
-
const authenticated = statePath !== null;
|
|
386
|
-
// Get session stats
|
|
387
|
-
const stats = this.sessionManager.getStats();
|
|
388
|
-
// Deep check: actually verify the chat UI loads
|
|
389
|
-
let chatUiAccessible;
|
|
390
|
-
let deepCheckNotebook;
|
|
391
|
-
if (args?.deep_check && authenticated) {
|
|
392
|
-
log.info(` 🔍 Running deep check - verifying chat UI loads...`);
|
|
393
|
-
try {
|
|
394
|
-
// Find a notebook to test with
|
|
395
|
-
let notebookUrl;
|
|
396
|
-
if (args.notebook_id) {
|
|
397
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
398
|
-
if (notebook) {
|
|
399
|
-
notebookUrl = notebook.url;
|
|
400
|
-
deepCheckNotebook = notebook.name || args.notebook_id;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
if (!notebookUrl) {
|
|
404
|
-
const activeNotebook = this.library.getActiveNotebook();
|
|
405
|
-
if (activeNotebook) {
|
|
406
|
-
notebookUrl = activeNotebook.url;
|
|
407
|
-
deepCheckNotebook = activeNotebook.name || "active notebook";
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
if (!notebookUrl) {
|
|
411
|
-
// Try to get any notebook from library
|
|
412
|
-
const notebooks = this.library.listNotebooks();
|
|
413
|
-
if (notebooks.length > 0) {
|
|
414
|
-
notebookUrl = notebooks[0].url;
|
|
415
|
-
deepCheckNotebook = notebooks[0].name || "first notebook";
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
if (notebookUrl) {
|
|
419
|
-
// Create a temporary session to test
|
|
420
|
-
const sessionId = `health-check-${Date.now()}`;
|
|
421
|
-
const session = await this.sessionManager.getOrCreateSession(sessionId, notebookUrl);
|
|
422
|
-
try {
|
|
423
|
-
const page = session.getPage();
|
|
424
|
-
if (page) {
|
|
425
|
-
// Wait for page to load
|
|
426
|
-
await page.waitForTimeout(3000);
|
|
427
|
-
// Check for chat input element
|
|
428
|
-
const chatInput = await page.$('textarea, [contenteditable="true"], .chat-input, .query-input, input[type="text"]');
|
|
429
|
-
chatUiAccessible = chatInput !== null;
|
|
430
|
-
if (!chatUiAccessible) {
|
|
431
|
-
// Also check for common NotebookLM chat selectors
|
|
432
|
-
const altSelectors = await page.$('.chat-container, .query-container, .message-input-container');
|
|
433
|
-
chatUiAccessible = altSelectors !== null;
|
|
434
|
-
}
|
|
435
|
-
log.info(` 📊 Chat UI accessible: ${chatUiAccessible}`);
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
chatUiAccessible = false;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
finally {
|
|
442
|
-
// Clean up the test session
|
|
443
|
-
await this.sessionManager.closeSession(sessionId);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
log.warning(` ⚠️ No notebook available for deep check`);
|
|
448
|
-
deepCheckNotebook = "none available";
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
catch (deepCheckError) {
|
|
452
|
-
log.warning(` ⚠️ Deep check failed: ${deepCheckError instanceof Error ? deepCheckError.message : String(deepCheckError)}`);
|
|
453
|
-
chatUiAccessible = false;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
const result = {
|
|
457
|
-
status: "ok",
|
|
458
|
-
authenticated,
|
|
459
|
-
notebook_url: CONFIG.notebookUrl || "not configured",
|
|
460
|
-
active_sessions: stats.active_sessions,
|
|
461
|
-
max_sessions: stats.max_sessions,
|
|
462
|
-
session_timeout: stats.session_timeout,
|
|
463
|
-
total_messages: stats.total_messages,
|
|
464
|
-
headless: CONFIG.headless,
|
|
465
|
-
auto_login_enabled: CONFIG.autoLoginEnabled,
|
|
466
|
-
stealth_enabled: CONFIG.stealthEnabled,
|
|
467
|
-
// Include deep check results if performed
|
|
468
|
-
...(args?.deep_check && {
|
|
469
|
-
chat_ui_accessible: chatUiAccessible,
|
|
470
|
-
deep_check_notebook: deepCheckNotebook,
|
|
471
|
-
}),
|
|
472
|
-
// Add troubleshooting tip if not authenticated or chat UI not accessible
|
|
473
|
-
...(((!authenticated) || (args?.deep_check && chatUiAccessible === false)) && {
|
|
474
|
-
troubleshooting_tip: chatUiAccessible === false
|
|
475
|
-
? "Chat UI not accessible. Session may be stale. Run re_auth(show_browser:true) to refresh."
|
|
476
|
-
: "Not authenticated. Run setup_auth(show_browser:true) to log in via a visible browser window. " +
|
|
477
|
-
"Do NOT call cleanup_data — it does not help with auth and is not needed here."
|
|
478
|
-
}),
|
|
479
|
-
};
|
|
480
|
-
log.success(`✅ [TOOL] get_health completed`);
|
|
481
|
-
return {
|
|
482
|
-
success: true,
|
|
483
|
-
data: result,
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
catch (error) {
|
|
487
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
488
|
-
log.error(`❌ [TOOL] get_health failed: ${errorMessage}`);
|
|
489
|
-
return {
|
|
490
|
-
success: false,
|
|
491
|
-
error: errorMessage,
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Handle setup_auth tool
|
|
497
|
-
*
|
|
498
|
-
* Opens a browser window for manual login with live progress updates.
|
|
499
|
-
* The operation waits synchronously for login completion (up to 10 minutes).
|
|
500
|
-
*/
|
|
501
|
-
async handleSetupAuth(args, sendProgress) {
|
|
502
|
-
const { show_browser } = args;
|
|
503
|
-
// CRITICAL: Send immediate progress to reset timeout from the very start
|
|
504
|
-
await sendProgress?.("Initializing authentication setup...", 0, 10);
|
|
505
|
-
log.info(`🔧 [TOOL] setup_auth called`);
|
|
506
|
-
if (show_browser !== undefined) {
|
|
507
|
-
log.info(` Show browser: ${show_browser}`);
|
|
508
|
-
}
|
|
509
|
-
// Guard: setup_auth ALWAYS clears all saved credentials before re-logging in.
|
|
510
|
-
// Calling it headlessly will wipe auth and then fail to restore it, leaving
|
|
511
|
-
// all sessions permanently unauthenticated. Require show_browser:true.
|
|
512
|
-
if (!show_browser) {
|
|
513
|
-
log.error("❌ setup_auth requires show_browser:true — cannot login interactively in headless mode");
|
|
514
|
-
return {
|
|
515
|
-
success: false,
|
|
516
|
-
authenticated: false,
|
|
517
|
-
error: "setup_auth requires show_browser:true. " +
|
|
518
|
-
"Calling it without a visible browser wipes your saved credentials then fails to restore them. " +
|
|
519
|
-
"Run the auth-now.mjs script instead, or pass show_browser:true.",
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
const startTime = Date.now();
|
|
523
|
-
try {
|
|
524
|
-
// Progress: Starting
|
|
525
|
-
await sendProgress?.("Preparing authentication browser...", 1, 10);
|
|
526
|
-
log.info(` 🌐 Opening browser for interactive login...`);
|
|
527
|
-
// Progress: Opening browser
|
|
528
|
-
await sendProgress?.("Opening browser window...", 2, 10);
|
|
529
|
-
// Perform setup with progress updates; pass show_browser so the caller
|
|
530
|
-
// can force a visible browser even when HEADLESS=true is set in env
|
|
531
|
-
const success = await this.authManager.performSetup(sendProgress, show_browser);
|
|
532
|
-
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
533
|
-
if (success) {
|
|
534
|
-
// Progress: Complete
|
|
535
|
-
await sendProgress?.("Authentication saved successfully!", 10, 10);
|
|
536
|
-
log.success(`✅ [TOOL] setup_auth completed (${durationSeconds.toFixed(1)}s)`);
|
|
537
|
-
// Audit: successful authentication
|
|
538
|
-
await audit.auth("setup_auth", true, { duration_seconds: durationSeconds });
|
|
539
|
-
await audit.tool("setup_auth", {}, true, Date.now() - startTime);
|
|
540
|
-
return {
|
|
541
|
-
success: true,
|
|
542
|
-
data: {
|
|
543
|
-
status: "authenticated",
|
|
544
|
-
message: "Successfully authenticated and saved browser state",
|
|
545
|
-
authenticated: true,
|
|
546
|
-
duration_seconds: durationSeconds,
|
|
547
|
-
},
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
log.error(`❌ [TOOL] setup_auth failed (${durationSeconds.toFixed(1)}s)`);
|
|
552
|
-
// Audit: failed authentication
|
|
553
|
-
await audit.auth("setup_auth", false, { reason: "cancelled_or_failed" });
|
|
554
|
-
await audit.tool("setup_auth", {}, false, Date.now() - startTime, "Authentication failed or was cancelled");
|
|
555
|
-
return {
|
|
556
|
-
success: false,
|
|
557
|
-
error: "Authentication failed or was cancelled",
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
catch (error) {
|
|
562
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
563
|
-
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
564
|
-
log.error(`❌ [TOOL] setup_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
|
|
565
|
-
// Audit: auth error
|
|
566
|
-
await audit.auth("setup_auth", false, { error: errorMessage });
|
|
567
|
-
await audit.tool("setup_auth", {}, false, Date.now() - startTime, errorMessage);
|
|
568
|
-
return {
|
|
569
|
-
success: false,
|
|
570
|
-
error: errorMessage,
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Handle re_auth tool
|
|
576
|
-
*
|
|
577
|
-
* Performs a complete re-authentication:
|
|
578
|
-
* 1. Closes all active browser sessions
|
|
579
|
-
* 2. Deletes all saved authentication data (cookies, Chrome profile)
|
|
580
|
-
* 3. Opens browser for fresh Google login
|
|
581
|
-
*
|
|
582
|
-
* Use for switching Google accounts or recovering from rate limits.
|
|
583
|
-
*/
|
|
584
|
-
async handleReAuth(args, sendProgress) {
|
|
585
|
-
const { show_browser } = args;
|
|
586
|
-
await sendProgress?.("Preparing re-authentication...", 0, 12);
|
|
587
|
-
log.info(`🔧 [TOOL] re_auth called`);
|
|
588
|
-
if (show_browser !== undefined) {
|
|
589
|
-
log.info(` Show browser: ${show_browser}`);
|
|
590
|
-
}
|
|
591
|
-
// Guard: re_auth requires a visible browser — Google login is interactive.
|
|
592
|
-
// Calling without show_browser:true would wipe saved credentials then fail,
|
|
593
|
-
// leaving all sessions permanently unauthenticated.
|
|
594
|
-
if (!show_browser) {
|
|
595
|
-
log.error("❌ re_auth requires show_browser:true — cannot login interactively in headless mode");
|
|
596
|
-
return {
|
|
597
|
-
success: false,
|
|
598
|
-
error: "re_auth requires show_browser:true. Google login is interactive and cannot complete " +
|
|
599
|
-
"in headless mode. Calling re_auth headlessly wipes your saved credentials then fails to " +
|
|
600
|
-
"restore them, destroying auth for all concurrent sessions. Pass show_browser:true.",
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
const startTime = Date.now();
|
|
604
|
-
try {
|
|
605
|
-
// 1. Close all active sessions
|
|
606
|
-
await sendProgress?.("Closing all active sessions...", 1, 12);
|
|
607
|
-
log.info(" 🛑 Closing all sessions...");
|
|
608
|
-
await this.sessionManager.closeAllSessions();
|
|
609
|
-
log.success(" ✅ All sessions closed");
|
|
610
|
-
// 2. Clear all auth data
|
|
611
|
-
await sendProgress?.("Clearing authentication data...", 2, 12);
|
|
612
|
-
log.info(" 🗑️ Clearing all auth data...");
|
|
613
|
-
await this.authManager.clearAllAuthData();
|
|
614
|
-
log.success(" ✅ Auth data cleared");
|
|
615
|
-
// 3. Perform fresh setup
|
|
616
|
-
await sendProgress?.("Starting fresh authentication...", 3, 12);
|
|
617
|
-
log.info(" 🌐 Starting fresh authentication setup...");
|
|
618
|
-
const success = await this.authManager.performSetup(sendProgress);
|
|
619
|
-
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
620
|
-
if (success) {
|
|
621
|
-
await sendProgress?.("Re-authentication complete!", 12, 12);
|
|
622
|
-
log.success(`✅ [TOOL] re_auth completed (${durationSeconds.toFixed(1)}s)`);
|
|
623
|
-
// Audit: successful re-auth
|
|
624
|
-
await audit.auth("re_auth", true, { duration_seconds: durationSeconds });
|
|
625
|
-
await audit.tool("re_auth", {}, true, Date.now() - startTime);
|
|
626
|
-
return {
|
|
627
|
-
success: true,
|
|
628
|
-
data: {
|
|
629
|
-
status: "authenticated",
|
|
630
|
-
message: "Successfully re-authenticated with new account. All previous sessions have been closed.",
|
|
631
|
-
authenticated: true,
|
|
632
|
-
duration_seconds: durationSeconds,
|
|
633
|
-
},
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
log.error(`❌ [TOOL] re_auth failed (${durationSeconds.toFixed(1)}s)`);
|
|
638
|
-
// Audit: failed re-auth
|
|
639
|
-
await audit.auth("re_auth", false, { reason: "cancelled_or_failed" });
|
|
640
|
-
await audit.tool("re_auth", {}, false, Date.now() - startTime, "Re-authentication failed or was cancelled");
|
|
641
|
-
return {
|
|
642
|
-
success: false,
|
|
643
|
-
error: "Re-authentication failed or was cancelled",
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
catch (error) {
|
|
648
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
649
|
-
const durationSeconds = (Date.now() - startTime) / 1000;
|
|
650
|
-
log.error(`❌ [TOOL] re_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
|
|
651
|
-
// Audit: re-auth error
|
|
652
|
-
await audit.auth("re_auth", false, { error: errorMessage });
|
|
653
|
-
await audit.tool("re_auth", {}, false, Date.now() - startTime, errorMessage);
|
|
654
|
-
return {
|
|
655
|
-
success: false,
|
|
656
|
-
error: errorMessage,
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* Handle add_notebook tool
|
|
662
|
-
*/
|
|
663
|
-
async handleAddNotebook(args) {
|
|
664
|
-
log.info(`🔧 [TOOL] add_notebook called`);
|
|
665
|
-
log.info(` Name: ${args.name}`);
|
|
666
|
-
try {
|
|
667
|
-
const notebook = this.library.addNotebook(args);
|
|
668
|
-
log.success(`✅ [TOOL] add_notebook completed: ${notebook.id}`);
|
|
669
|
-
return {
|
|
670
|
-
success: true,
|
|
671
|
-
data: { notebook },
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
catch (error) {
|
|
675
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
676
|
-
log.error(`❌ [TOOL] add_notebook failed: ${errorMessage}`);
|
|
677
|
-
return {
|
|
678
|
-
success: false,
|
|
679
|
-
error: errorMessage,
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Handle list_notebooks tool
|
|
685
|
-
*/
|
|
686
|
-
async handleListNotebooks() {
|
|
687
|
-
log.info(`🔧 [TOOL] list_notebooks called`);
|
|
688
|
-
try {
|
|
689
|
-
const notebooks = this.library.listNotebooks();
|
|
690
|
-
log.success(`✅ [TOOL] list_notebooks completed (${notebooks.length} notebooks)`);
|
|
691
|
-
return {
|
|
692
|
-
success: true,
|
|
693
|
-
data: { notebooks },
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
catch (error) {
|
|
697
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
698
|
-
log.error(`❌ [TOOL] list_notebooks failed: ${errorMessage}`);
|
|
699
|
-
return {
|
|
700
|
-
success: false,
|
|
701
|
-
error: errorMessage,
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Handle get_notebook tool
|
|
707
|
-
*/
|
|
708
|
-
async handleGetNotebook(args) {
|
|
709
|
-
log.info(`🔧 [TOOL] get_notebook called`);
|
|
710
|
-
log.info(` ID: ${args.id}`);
|
|
711
|
-
try {
|
|
712
|
-
const notebook = this.library.getNotebook(args.id);
|
|
713
|
-
if (!notebook) {
|
|
714
|
-
log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
|
|
715
|
-
return {
|
|
716
|
-
success: false,
|
|
717
|
-
error: `Notebook not found: ${args.id}`,
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
log.success(`✅ [TOOL] get_notebook completed: ${notebook.name}`);
|
|
721
|
-
return {
|
|
722
|
-
success: true,
|
|
723
|
-
data: { notebook },
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
catch (error) {
|
|
727
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
728
|
-
log.error(`❌ [TOOL] get_notebook failed: ${errorMessage}`);
|
|
729
|
-
return {
|
|
730
|
-
success: false,
|
|
731
|
-
error: errorMessage,
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Handle select_notebook tool
|
|
737
|
-
*/
|
|
738
|
-
async handleSelectNotebook(args) {
|
|
739
|
-
log.info(`🔧 [TOOL] select_notebook called`);
|
|
740
|
-
log.info(` ID: ${args.id}`);
|
|
741
|
-
try {
|
|
742
|
-
const notebook = this.library.selectNotebook(args.id);
|
|
743
|
-
log.success(`✅ [TOOL] select_notebook completed: ${notebook.name}`);
|
|
744
|
-
return {
|
|
745
|
-
success: true,
|
|
746
|
-
data: { notebook },
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
catch (error) {
|
|
750
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
751
|
-
log.error(`❌ [TOOL] select_notebook failed: ${errorMessage}`);
|
|
752
|
-
return {
|
|
753
|
-
success: false,
|
|
754
|
-
error: errorMessage,
|
|
755
|
-
};
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
/**
|
|
759
|
-
* Handle update_notebook tool
|
|
760
|
-
*/
|
|
761
|
-
async handleUpdateNotebook(args) {
|
|
762
|
-
log.info(`🔧 [TOOL] update_notebook called`);
|
|
763
|
-
log.info(` ID: ${args.id}`);
|
|
764
|
-
try {
|
|
765
|
-
const notebook = this.library.updateNotebook(args);
|
|
766
|
-
log.success(`✅ [TOOL] update_notebook completed: ${notebook.name}`);
|
|
767
|
-
return {
|
|
768
|
-
success: true,
|
|
769
|
-
data: { notebook },
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
catch (error) {
|
|
773
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
774
|
-
log.error(`❌ [TOOL] update_notebook failed: ${errorMessage}`);
|
|
775
|
-
return {
|
|
776
|
-
success: false,
|
|
777
|
-
error: errorMessage,
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Handle remove_notebook tool
|
|
783
|
-
*/
|
|
784
|
-
async handleRemoveNotebook(args) {
|
|
785
|
-
log.info(`🔧 [TOOL] remove_notebook called`);
|
|
786
|
-
log.info(` ID: ${args.id}`);
|
|
787
|
-
try {
|
|
788
|
-
const notebook = this.library.getNotebook(args.id);
|
|
789
|
-
if (!notebook) {
|
|
790
|
-
log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
|
|
791
|
-
return {
|
|
792
|
-
success: false,
|
|
793
|
-
error: `Notebook not found: ${args.id}`,
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
const removed = this.library.removeNotebook(args.id);
|
|
797
|
-
if (removed) {
|
|
798
|
-
const closedSessions = await this.sessionManager.closeSessionsForNotebook(notebook.url);
|
|
799
|
-
log.success(`✅ [TOOL] remove_notebook completed`);
|
|
800
|
-
return {
|
|
801
|
-
success: true,
|
|
802
|
-
data: { removed: true, closed_sessions: closedSessions },
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
else {
|
|
806
|
-
log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
|
|
807
|
-
return {
|
|
808
|
-
success: false,
|
|
809
|
-
error: `Notebook not found: ${args.id}`,
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
catch (error) {
|
|
814
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
815
|
-
log.error(`❌ [TOOL] remove_notebook failed: ${errorMessage}`);
|
|
816
|
-
return {
|
|
817
|
-
success: false,
|
|
818
|
-
error: errorMessage,
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
/**
|
|
823
|
-
* Handle search_notebooks tool
|
|
824
|
-
*/
|
|
825
|
-
async handleSearchNotebooks(args) {
|
|
826
|
-
log.info(`🔧 [TOOL] search_notebooks called`);
|
|
827
|
-
log.info(` Query: "${args.query}"`);
|
|
828
|
-
try {
|
|
829
|
-
const notebooks = this.library.searchNotebooks(args.query);
|
|
830
|
-
log.success(`✅ [TOOL] search_notebooks completed (${notebooks.length} results)`);
|
|
831
|
-
return {
|
|
832
|
-
success: true,
|
|
833
|
-
data: { notebooks },
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
catch (error) {
|
|
837
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
838
|
-
log.error(`❌ [TOOL] search_notebooks failed: ${errorMessage}`);
|
|
839
|
-
return {
|
|
840
|
-
success: false,
|
|
841
|
-
error: errorMessage,
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
/**
|
|
846
|
-
* Handle get_library_stats tool
|
|
847
|
-
*/
|
|
848
|
-
async handleGetLibraryStats() {
|
|
849
|
-
log.info(`🔧 [TOOL] get_library_stats called`);
|
|
850
|
-
try {
|
|
851
|
-
const stats = this.library.getStats();
|
|
852
|
-
log.success(`✅ [TOOL] get_library_stats completed`);
|
|
853
|
-
return {
|
|
854
|
-
success: true,
|
|
855
|
-
data: stats,
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
catch (error) {
|
|
859
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
860
|
-
log.error(`❌ [TOOL] get_library_stats failed: ${errorMessage}`);
|
|
861
|
-
return {
|
|
862
|
-
success: false,
|
|
863
|
-
error: errorMessage,
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Handle export_library tool
|
|
869
|
-
*
|
|
870
|
-
* Exports notebook library to a backup file (JSON or CSV).
|
|
871
|
-
*/
|
|
872
|
-
async handleExportLibrary(args) {
|
|
873
|
-
const format = args.format || "json";
|
|
874
|
-
log.info(`🔧 [TOOL] export_library called`);
|
|
875
|
-
log.info(` Format: ${format}`);
|
|
876
|
-
try {
|
|
877
|
-
const notebooks = this.library.listNotebooks();
|
|
878
|
-
const stats = this.library.getStats();
|
|
879
|
-
// Generate default output path if not provided
|
|
880
|
-
const date = new Date().toISOString().split("T")[0];
|
|
881
|
-
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
882
|
-
const defaultPath = `${homeDir}/notebooklm-library-backup-${date}.${format}`;
|
|
883
|
-
const outputPath = args.output_path || defaultPath;
|
|
884
|
-
let content;
|
|
885
|
-
if (format === "csv") {
|
|
886
|
-
// CSV format: name, url, topics, last_used, use_count
|
|
887
|
-
const headers = ["name", "url", "topics", "description", "last_used", "use_count"];
|
|
888
|
-
const rows = notebooks.map((nb) => [
|
|
889
|
-
`"${(nb.name || "").replace(/"/g, '""')}"`,
|
|
890
|
-
`"${nb.url}"`,
|
|
891
|
-
`"${(nb.topics || []).join("; ")}"`,
|
|
892
|
-
`"${(nb.description || "").replace(/"/g, '""')}"`,
|
|
893
|
-
nb.last_used || "",
|
|
894
|
-
String(nb.use_count || 0),
|
|
895
|
-
]);
|
|
896
|
-
content = [headers.join(","), ...rows.map((r) => r.join(","))].join("\n");
|
|
897
|
-
}
|
|
898
|
-
else {
|
|
899
|
-
// JSON format: full library data
|
|
900
|
-
content = JSON.stringify({
|
|
901
|
-
exported_at: new Date().toISOString(),
|
|
902
|
-
version: "1.0",
|
|
903
|
-
stats: {
|
|
904
|
-
total_notebooks: stats.total_notebooks,
|
|
905
|
-
total_queries: stats.total_queries,
|
|
906
|
-
},
|
|
907
|
-
notebooks: notebooks,
|
|
908
|
-
}, null, 2);
|
|
909
|
-
}
|
|
910
|
-
// Write file with secure permissions
|
|
911
|
-
const fs = await import("fs");
|
|
912
|
-
fs.writeFileSync(outputPath, content, { mode: 0o600 });
|
|
913
|
-
const fileStats = fs.statSync(outputPath);
|
|
914
|
-
log.success(`✅ [TOOL] export_library completed: ${outputPath}`);
|
|
915
|
-
return {
|
|
916
|
-
success: true,
|
|
917
|
-
data: {
|
|
918
|
-
file_path: outputPath,
|
|
919
|
-
format,
|
|
920
|
-
notebook_count: notebooks.length,
|
|
921
|
-
size_bytes: fileStats.size,
|
|
922
|
-
},
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
catch (error) {
|
|
926
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
927
|
-
log.error(`❌ [TOOL] export_library failed: ${errorMessage}`);
|
|
928
|
-
return {
|
|
929
|
-
success: false,
|
|
930
|
-
error: errorMessage,
|
|
931
|
-
};
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
/**
|
|
935
|
-
* Handle get_project_info tool
|
|
936
|
-
*
|
|
937
|
-
* Returns current project context and library location.
|
|
938
|
-
*/
|
|
939
|
-
async handleGetProjectInfo() {
|
|
940
|
-
log.info(`🔧 [TOOL] get_project_info called`);
|
|
941
|
-
try {
|
|
942
|
-
// Get info from the library instance
|
|
943
|
-
const projectInfo = this.library.getProjectInfo();
|
|
944
|
-
const libraryPath = this.library.getLibraryPath();
|
|
945
|
-
const isProjectLibrary = this.library.isProjectLibrary();
|
|
946
|
-
// Also detect what project would be detected from cwd
|
|
947
|
-
const { NotebookLibrary: NL } = await import("../library/notebook-library.js");
|
|
948
|
-
const detectedProject = NL.detectCurrentProject();
|
|
949
|
-
log.success(`✅ [TOOL] get_project_info completed`);
|
|
950
|
-
return {
|
|
951
|
-
success: true,
|
|
952
|
-
data: {
|
|
953
|
-
project: projectInfo,
|
|
954
|
-
library_path: libraryPath,
|
|
955
|
-
is_project_library: isProjectLibrary,
|
|
956
|
-
detected_project: detectedProject,
|
|
957
|
-
},
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
catch (error) {
|
|
961
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
962
|
-
log.error(`❌ [TOOL] get_project_info failed: ${errorMessage}`);
|
|
963
|
-
return {
|
|
964
|
-
success: false,
|
|
965
|
-
error: errorMessage,
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
/**
|
|
970
|
-
* Handle get_quota tool
|
|
971
|
-
*
|
|
972
|
-
* Returns current quota status including license tier, usage, and limits.
|
|
973
|
-
* If sync=true, navigates to NotebookLM to fetch actual quota from Google.
|
|
974
|
-
*/
|
|
975
|
-
async handleGetQuota(args = {}) {
|
|
976
|
-
const { sync = false } = args;
|
|
977
|
-
log.info(`🔧 [TOOL] get_quota called (sync=${sync})`);
|
|
978
|
-
try {
|
|
979
|
-
const quotaManager = getQuotaManager();
|
|
980
|
-
let syncedFromGoogle = false;
|
|
981
|
-
let googleQuota = null;
|
|
982
|
-
let rateLimitDetected = false;
|
|
983
|
-
// If sync requested, navigate to NotebookLM and scrape quota
|
|
984
|
-
if (sync) {
|
|
985
|
-
log.info("📊 Syncing quota from Google NotebookLM...");
|
|
986
|
-
try {
|
|
987
|
-
// Get the shared context manager from session manager
|
|
988
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
989
|
-
const context = await contextManager.getOrCreateContext();
|
|
990
|
-
// Create a new page to check quota
|
|
991
|
-
const page = await context.newPage();
|
|
992
|
-
try {
|
|
993
|
-
// Navigate to NotebookLM homepage
|
|
994
|
-
await page.goto("https://notebooklm.google.com/", {
|
|
995
|
-
waitUntil: "networkidle",
|
|
996
|
-
timeout: 30000,
|
|
997
|
-
});
|
|
998
|
-
// Wait for page to load
|
|
999
|
-
await page.waitForTimeout(2000);
|
|
1000
|
-
// Update quota from UI
|
|
1001
|
-
const syncResult = await quotaManager.updateFromUI(page);
|
|
1002
|
-
syncedFromGoogle = true;
|
|
1003
|
-
googleQuota = syncResult.queryUsageFromGoogle;
|
|
1004
|
-
rateLimitDetected = syncResult.rateLimitDetected;
|
|
1005
|
-
log.success(`✅ Synced quota from Google: ${googleQuota ? `${googleQuota.used}/${googleQuota.limit}` : "usage not displayed in UI"}`);
|
|
1006
|
-
}
|
|
1007
|
-
finally {
|
|
1008
|
-
await page.close();
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
catch (syncError) {
|
|
1012
|
-
const syncErrorMsg = syncError instanceof Error ? syncError.message : String(syncError);
|
|
1013
|
-
log.warning(`⚠️ Could not sync from Google: ${syncErrorMsg}. Using local tracking.`);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
const detailedStatus = quotaManager.getDetailedStatus();
|
|
1017
|
-
const settings = quotaManager.getSettings();
|
|
1018
|
-
log.success(`✅ [TOOL] get_quota completed (tier: ${detailedStatus.tier}, ${detailedStatus.queries.remaining} queries remaining, synced=${syncedFromGoogle})`);
|
|
1019
|
-
return {
|
|
1020
|
-
success: true,
|
|
1021
|
-
data: {
|
|
1022
|
-
tier: detailedStatus.tier,
|
|
1023
|
-
notebooks: {
|
|
1024
|
-
used: detailedStatus.notebooks.used,
|
|
1025
|
-
limit: detailedStatus.notebooks.limit,
|
|
1026
|
-
remaining: detailedStatus.notebooks.remaining,
|
|
1027
|
-
percent: detailedStatus.notebooks.percentUsed,
|
|
1028
|
-
},
|
|
1029
|
-
sources: detailedStatus.sources,
|
|
1030
|
-
queries: {
|
|
1031
|
-
used: detailedStatus.queries.used,
|
|
1032
|
-
limit: detailedStatus.queries.limit,
|
|
1033
|
-
remaining: detailedStatus.queries.remaining,
|
|
1034
|
-
percent: detailedStatus.queries.percentUsed,
|
|
1035
|
-
should_stop: detailedStatus.queries.shouldStop,
|
|
1036
|
-
reset_time: detailedStatus.queries.resetTime,
|
|
1037
|
-
},
|
|
1038
|
-
warnings: detailedStatus.warnings,
|
|
1039
|
-
auto_detected: settings.autoDetected,
|
|
1040
|
-
last_updated: settings.usage.lastUpdated,
|
|
1041
|
-
synced_from_google: syncedFromGoogle,
|
|
1042
|
-
google_quota: googleQuota,
|
|
1043
|
-
rate_limit_detected: rateLimitDetected,
|
|
1044
|
-
},
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
catch (error) {
|
|
1048
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1049
|
-
log.error(`❌ [TOOL] get_quota failed: ${errorMessage}`);
|
|
1050
|
-
return {
|
|
1051
|
-
success: false,
|
|
1052
|
-
error: errorMessage,
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
/**
|
|
1057
|
-
* Handle set_quota_tier tool
|
|
1058
|
-
*
|
|
1059
|
-
* Manually set the license tier to override auto-detection.
|
|
1060
|
-
*/
|
|
1061
|
-
async handleSetQuotaTier(args) {
|
|
1062
|
-
log.info(`🔧 [TOOL] set_quota_tier called`);
|
|
1063
|
-
log.info(` Tier: ${args.tier}`);
|
|
1064
|
-
try {
|
|
1065
|
-
const quotaManager = getQuotaManager();
|
|
1066
|
-
quotaManager.setTier(args.tier);
|
|
1067
|
-
const settings = quotaManager.getSettings();
|
|
1068
|
-
log.success(`✅ [TOOL] set_quota_tier completed (tier: ${args.tier})`);
|
|
1069
|
-
return {
|
|
1070
|
-
success: true,
|
|
1071
|
-
data: {
|
|
1072
|
-
tier: settings.tier,
|
|
1073
|
-
limits: {
|
|
1074
|
-
notebooks: settings.limits.notebooks,
|
|
1075
|
-
sourcesPerNotebook: settings.limits.sourcesPerNotebook,
|
|
1076
|
-
queriesPerDay: settings.limits.queriesPerDay,
|
|
1077
|
-
},
|
|
1078
|
-
message: `License tier set to ${args.tier}. Limits updated accordingly.`,
|
|
1079
|
-
},
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
catch (error) {
|
|
1083
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1084
|
-
log.error(`❌ [TOOL] set_quota_tier failed: ${errorMessage}`);
|
|
1085
|
-
return {
|
|
1086
|
-
success: false,
|
|
1087
|
-
error: errorMessage,
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
/**
|
|
1092
|
-
* Handle create_notebook tool
|
|
1093
|
-
*
|
|
1094
|
-
* Creates a new NotebookLM notebook with sources programmatically.
|
|
1095
|
-
*/
|
|
1096
|
-
async handleCreateNotebook(args, sendProgress) {
|
|
1097
|
-
log.info(`🔧 [TOOL] create_notebook called`);
|
|
1098
|
-
log.info(` Name: ${args.name}`);
|
|
1099
|
-
log.info(` Sources: ${args.sources?.length || 0}`);
|
|
1100
|
-
try {
|
|
1101
|
-
// Validate inputs
|
|
1102
|
-
if (!args.name || typeof args.name !== "string") {
|
|
1103
|
-
throw new Error("Notebook name is required");
|
|
1104
|
-
}
|
|
1105
|
-
if (!args.sources || !Array.isArray(args.sources) || args.sources.length === 0) {
|
|
1106
|
-
throw new Error("At least one source is required");
|
|
1107
|
-
}
|
|
1108
|
-
// Validate each source
|
|
1109
|
-
for (const source of args.sources) {
|
|
1110
|
-
if (!source.type || !["url", "text", "file"].includes(source.type)) {
|
|
1111
|
-
throw new Error(`Invalid source type: ${source.type}. Must be url, text, or file.`);
|
|
1112
|
-
}
|
|
1113
|
-
if (!source.value || typeof source.value !== "string") {
|
|
1114
|
-
throw new Error("Source value is required");
|
|
1115
|
-
}
|
|
1116
|
-
if (source.type === "url") {
|
|
1117
|
-
try {
|
|
1118
|
-
new URL(source.value);
|
|
1119
|
-
}
|
|
1120
|
-
catch {
|
|
1121
|
-
throw new Error(`Invalid URL: ${source.value}`);
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
}
|
|
1125
|
-
// === QUOTA CHECK ===
|
|
1126
|
-
const quotaManager = getQuotaManager();
|
|
1127
|
-
const canCreate = quotaManager.canCreateNotebook();
|
|
1128
|
-
if (!canCreate.allowed) {
|
|
1129
|
-
log.warning(`⚠️ Quota limit: ${canCreate.reason}`);
|
|
1130
|
-
return {
|
|
1131
|
-
success: false,
|
|
1132
|
-
error: canCreate.reason || "Notebook quota limit reached",
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
// Check source limit
|
|
1136
|
-
const sourceLimits = quotaManager.getLimits();
|
|
1137
|
-
if (args.sources.length > sourceLimits.sourcesPerNotebook) {
|
|
1138
|
-
const reason = `Too many sources (${args.sources.length}). Limit is ${sourceLimits.sourcesPerNotebook} per notebook.`;
|
|
1139
|
-
log.warning(`⚠️ Quota limit: ${reason}`);
|
|
1140
|
-
return {
|
|
1141
|
-
success: false,
|
|
1142
|
-
error: reason,
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
// Get the shared context manager from session manager
|
|
1146
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1147
|
-
// Create notebook
|
|
1148
|
-
const creator = new NotebookCreator(this.authManager, contextManager);
|
|
1149
|
-
const result = await creator.createNotebook({
|
|
1150
|
-
name: args.name,
|
|
1151
|
-
sources: args.sources,
|
|
1152
|
-
sendProgress,
|
|
1153
|
-
browserOptions: args.browser_options || (args.show_browser ? { show: true } : undefined),
|
|
1154
|
-
});
|
|
1155
|
-
// Auto-add to library if requested (default: true)
|
|
1156
|
-
if (args.auto_add_to_library !== false) {
|
|
1157
|
-
try {
|
|
1158
|
-
this.library.addNotebook({
|
|
1159
|
-
url: result.url,
|
|
1160
|
-
name: args.name,
|
|
1161
|
-
description: args.description || `Created ${new Date().toLocaleDateString()}`,
|
|
1162
|
-
topics: args.topics || [],
|
|
1163
|
-
});
|
|
1164
|
-
log.success(`✅ Added notebook to library: ${args.name}`);
|
|
1165
|
-
}
|
|
1166
|
-
catch (libError) {
|
|
1167
|
-
log.warning(`⚠️ Failed to add to library: ${libError}`);
|
|
1168
|
-
// Don't fail the whole operation
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
// Update quota tracking
|
|
1172
|
-
quotaManager.incrementNotebookCount();
|
|
1173
|
-
// Audit log
|
|
1174
|
-
await audit.tool("create_notebook", {
|
|
1175
|
-
name: args.name,
|
|
1176
|
-
sourceCount: args.sources.length,
|
|
1177
|
-
url: result.url,
|
|
1178
|
-
}, true, 0);
|
|
1179
|
-
log.success(`✅ [TOOL] create_notebook completed: ${result.url}`);
|
|
1180
|
-
return {
|
|
1181
|
-
success: true,
|
|
1182
|
-
data: result,
|
|
1183
|
-
};
|
|
1184
|
-
}
|
|
1185
|
-
catch (error) {
|
|
1186
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1187
|
-
log.error(`❌ [TOOL] create_notebook failed: ${errorMessage}`);
|
|
1188
|
-
await audit.tool("create_notebook", {
|
|
1189
|
-
name: args.name,
|
|
1190
|
-
error: errorMessage,
|
|
1191
|
-
}, false, 0, errorMessage);
|
|
1192
|
-
return {
|
|
1193
|
-
success: false,
|
|
1194
|
-
error: errorMessage,
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* Handle batch_create_notebooks tool
|
|
1200
|
-
*
|
|
1201
|
-
* Creates multiple notebooks in a single batch operation.
|
|
1202
|
-
*/
|
|
1203
|
-
async handleBatchCreateNotebooks(args, sendProgress) {
|
|
1204
|
-
log.info(`🔧 [TOOL] batch_create_notebooks called`);
|
|
1205
|
-
log.info(` Notebooks: ${args.notebooks.length}`);
|
|
1206
|
-
log.info(` Stop on error: ${args.stop_on_error || false}`);
|
|
1207
|
-
try {
|
|
1208
|
-
// Validate input
|
|
1209
|
-
if (!args.notebooks || !Array.isArray(args.notebooks)) {
|
|
1210
|
-
throw new Error("notebooks array is required");
|
|
1211
|
-
}
|
|
1212
|
-
if (args.notebooks.length === 0) {
|
|
1213
|
-
throw new Error("At least one notebook is required");
|
|
1214
|
-
}
|
|
1215
|
-
if (args.notebooks.length > 10) {
|
|
1216
|
-
throw new Error("Maximum 10 notebooks per batch");
|
|
1217
|
-
}
|
|
1218
|
-
const results = [];
|
|
1219
|
-
const total = args.notebooks.length;
|
|
1220
|
-
let succeeded = 0;
|
|
1221
|
-
let failed = 0;
|
|
1222
|
-
for (let i = 0; i < args.notebooks.length; i++) {
|
|
1223
|
-
const notebook = args.notebooks[i];
|
|
1224
|
-
await sendProgress?.(`Creating notebook ${i + 1}/${total}: ${notebook.name}`, i, total);
|
|
1225
|
-
log.info(` 📓 Creating notebook ${i + 1}/${total}: ${notebook.name}`);
|
|
1226
|
-
try {
|
|
1227
|
-
const result = await this.handleCreateNotebook({
|
|
1228
|
-
name: notebook.name,
|
|
1229
|
-
sources: notebook.sources,
|
|
1230
|
-
description: notebook.description,
|
|
1231
|
-
topics: notebook.topics,
|
|
1232
|
-
auto_add_to_library: true,
|
|
1233
|
-
show_browser: args.show_browser,
|
|
1234
|
-
});
|
|
1235
|
-
if (result.success && result.data) {
|
|
1236
|
-
results.push({
|
|
1237
|
-
name: notebook.name,
|
|
1238
|
-
success: true,
|
|
1239
|
-
url: result.data.url,
|
|
1240
|
-
});
|
|
1241
|
-
succeeded++;
|
|
1242
|
-
log.success(` ✅ Created: ${result.data.url}`);
|
|
1243
|
-
}
|
|
1244
|
-
else {
|
|
1245
|
-
results.push({
|
|
1246
|
-
name: notebook.name,
|
|
1247
|
-
success: false,
|
|
1248
|
-
error: result.error || "Unknown error",
|
|
1249
|
-
});
|
|
1250
|
-
failed++;
|
|
1251
|
-
log.error(` ❌ Failed: ${result.error}`);
|
|
1252
|
-
if (args.stop_on_error) {
|
|
1253
|
-
log.warning(` ⚠️ Stopping batch due to error (stop_on_error=true)`);
|
|
1254
|
-
break;
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
catch (error) {
|
|
1259
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1260
|
-
results.push({
|
|
1261
|
-
name: notebook.name,
|
|
1262
|
-
success: false,
|
|
1263
|
-
error: errorMessage,
|
|
1264
|
-
});
|
|
1265
|
-
failed++;
|
|
1266
|
-
log.error(` ❌ Exception: ${errorMessage}`);
|
|
1267
|
-
if (args.stop_on_error) {
|
|
1268
|
-
log.warning(` ⚠️ Stopping batch due to exception (stop_on_error=true)`);
|
|
1269
|
-
break;
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
// Delay between notebooks to avoid rate limiting
|
|
1273
|
-
if (i < args.notebooks.length - 1) {
|
|
1274
|
-
const delay = 2000 + Math.random() * 2000; // 2-4 seconds
|
|
1275
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
await sendProgress?.(`Batch complete: ${succeeded}/${total} succeeded`, total, total);
|
|
1279
|
-
log.success(`✅ [TOOL] batch_create_notebooks completed: ${succeeded}/${total} succeeded`);
|
|
1280
|
-
return {
|
|
1281
|
-
success: failed === 0,
|
|
1282
|
-
data: {
|
|
1283
|
-
total,
|
|
1284
|
-
succeeded,
|
|
1285
|
-
failed,
|
|
1286
|
-
results,
|
|
1287
|
-
},
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
catch (error) {
|
|
1291
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1292
|
-
log.error(`❌ [TOOL] batch_create_notebooks failed: ${errorMessage}`);
|
|
1293
|
-
return {
|
|
1294
|
-
success: false,
|
|
1295
|
-
error: errorMessage,
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
/**
|
|
1300
|
-
* Handle sync_library tool
|
|
1301
|
-
*
|
|
1302
|
-
* Syncs local library with actual NotebookLM notebooks.
|
|
1303
|
-
*/
|
|
1304
|
-
async handleSyncLibrary(args) {
|
|
1305
|
-
log.info(`🔧 [TOOL] sync_library called`);
|
|
1306
|
-
log.info(` Auto-fix: ${args.auto_fix || false}`);
|
|
1307
|
-
log.info(` Show browser: ${args.show_browser || false}`);
|
|
1308
|
-
try {
|
|
1309
|
-
// Get the shared context manager from session manager
|
|
1310
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1311
|
-
// Sync library
|
|
1312
|
-
const sync = new NotebookSync(this.authManager, contextManager, this.library);
|
|
1313
|
-
const result = await sync.syncLibrary({
|
|
1314
|
-
autoFix: args.auto_fix,
|
|
1315
|
-
showBrowser: args.show_browser,
|
|
1316
|
-
});
|
|
1317
|
-
// Audit log
|
|
1318
|
-
await audit.tool("sync_library", {
|
|
1319
|
-
matched: result.matched.length,
|
|
1320
|
-
stale: result.staleEntries.length,
|
|
1321
|
-
missing: result.missingNotebooks.length,
|
|
1322
|
-
autoFix: args.auto_fix,
|
|
1323
|
-
}, true, 0);
|
|
1324
|
-
log.success(`✅ [TOOL] sync_library completed`);
|
|
1325
|
-
return {
|
|
1326
|
-
success: true,
|
|
1327
|
-
data: result,
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
|
-
catch (error) {
|
|
1331
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1332
|
-
log.error(`❌ [TOOL] sync_library failed: ${errorMessage}`);
|
|
1333
|
-
await audit.tool("sync_library", {
|
|
1334
|
-
error: errorMessage,
|
|
1335
|
-
}, false, 0, errorMessage);
|
|
1336
|
-
return {
|
|
1337
|
-
success: false,
|
|
1338
|
-
error: errorMessage,
|
|
1339
|
-
};
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Handle cleanup_data tool
|
|
1344
|
-
*
|
|
1345
|
-
* ULTRATHINK Deep Cleanup - scans entire system for ALL NotebookLM MCP files
|
|
1346
|
-
*/
|
|
1347
|
-
async handleCleanupData(args) {
|
|
1348
|
-
const { confirm, preserve_library = false } = args;
|
|
1349
|
-
log.info(`🔧 [TOOL] cleanup_data called`);
|
|
1350
|
-
log.info(` Confirm: ${confirm}`);
|
|
1351
|
-
log.info(` Preserve Library: ${preserve_library}`);
|
|
1352
|
-
const cleanupManager = new CleanupManager();
|
|
1353
|
-
try {
|
|
1354
|
-
// Always run in deep mode
|
|
1355
|
-
const mode = "deep";
|
|
1356
|
-
if (!confirm) {
|
|
1357
|
-
// Preview mode - show what would be deleted
|
|
1358
|
-
log.info(` 📋 Generating cleanup preview (mode: ${mode})...`);
|
|
1359
|
-
const preview = await cleanupManager.getCleanupPaths(mode, preserve_library);
|
|
1360
|
-
const platformInfo = cleanupManager.getPlatformInfo();
|
|
1361
|
-
log.info(` Found ${preview.totalPaths.length} items (${cleanupManager.formatBytes(preview.totalSizeBytes)})`);
|
|
1362
|
-
log.info(` Platform: ${platformInfo.platform}`);
|
|
1363
|
-
return {
|
|
1364
|
-
success: true,
|
|
1365
|
-
data: {
|
|
1366
|
-
status: "preview",
|
|
1367
|
-
mode,
|
|
1368
|
-
preview: {
|
|
1369
|
-
categories: preview.categories,
|
|
1370
|
-
totalPaths: preview.totalPaths.length,
|
|
1371
|
-
totalSizeBytes: preview.totalSizeBytes,
|
|
1372
|
-
},
|
|
1373
|
-
},
|
|
1374
|
-
};
|
|
1375
|
-
}
|
|
1376
|
-
else {
|
|
1377
|
-
// Cleanup mode - actually delete files
|
|
1378
|
-
log.info(` 🗑️ Performing cleanup (mode: ${mode})...`);
|
|
1379
|
-
const result = await cleanupManager.performCleanup(mode, preserve_library);
|
|
1380
|
-
if (result.success) {
|
|
1381
|
-
log.success(`✅ [TOOL] cleanup_data completed - deleted ${result.deletedPaths.length} items`);
|
|
1382
|
-
}
|
|
1383
|
-
else {
|
|
1384
|
-
log.warning(`⚠️ [TOOL] cleanup_data completed with ${result.failedPaths.length} errors`);
|
|
1385
|
-
}
|
|
1386
|
-
return {
|
|
1387
|
-
success: result.success,
|
|
1388
|
-
data: {
|
|
1389
|
-
status: result.success ? "completed" : "partial",
|
|
1390
|
-
mode,
|
|
1391
|
-
result: {
|
|
1392
|
-
deletedPaths: result.deletedPaths,
|
|
1393
|
-
failedPaths: result.failedPaths,
|
|
1394
|
-
totalSizeBytes: result.totalSizeBytes,
|
|
1395
|
-
categorySummary: result.categorySummary,
|
|
1396
|
-
},
|
|
1397
|
-
},
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
catch (error) {
|
|
1402
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1403
|
-
log.error(`❌ [TOOL] cleanup_data failed: ${errorMessage}`);
|
|
1404
|
-
return {
|
|
1405
|
-
success: false,
|
|
1406
|
-
error: errorMessage,
|
|
1407
|
-
};
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
/**
|
|
1411
|
-
* Handle list_sources tool
|
|
1412
|
-
*
|
|
1413
|
-
* List all sources in a NotebookLM notebook.
|
|
1414
|
-
*/
|
|
1415
|
-
async handleListSources(args) {
|
|
1416
|
-
log.info(`🔧 [TOOL] list_sources called`);
|
|
1417
|
-
try {
|
|
1418
|
-
// Resolve notebook URL
|
|
1419
|
-
let notebookUrl = args.notebook_url;
|
|
1420
|
-
if (!notebookUrl && args.notebook_id) {
|
|
1421
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
1422
|
-
if (!notebook) {
|
|
1423
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
1424
|
-
}
|
|
1425
|
-
notebookUrl = notebook.url;
|
|
1426
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
1427
|
-
}
|
|
1428
|
-
else if (!notebookUrl) {
|
|
1429
|
-
const active = this.library.getActiveNotebook();
|
|
1430
|
-
if (active) {
|
|
1431
|
-
notebookUrl = active.url;
|
|
1432
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
1433
|
-
}
|
|
1434
|
-
else {
|
|
1435
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
// Validate URL
|
|
1439
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
1440
|
-
// Get the shared context manager from session manager
|
|
1441
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1442
|
-
// List sources
|
|
1443
|
-
const sourceManager = new SourceManager(this.authManager, contextManager);
|
|
1444
|
-
const result = await sourceManager.listSources(safeUrl);
|
|
1445
|
-
log.success(`✅ [TOOL] list_sources completed (${result.count} sources)`);
|
|
1446
|
-
return {
|
|
1447
|
-
success: true,
|
|
1448
|
-
data: result,
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
catch (error) {
|
|
1452
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1453
|
-
log.error(`❌ [TOOL] list_sources failed: ${errorMessage}`);
|
|
1454
|
-
return {
|
|
1455
|
-
success: false,
|
|
1456
|
-
error: errorMessage,
|
|
1457
|
-
};
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
/**
|
|
1461
|
-
* Handle add_source tool
|
|
1462
|
-
*
|
|
1463
|
-
* Add a source to an existing NotebookLM notebook.
|
|
1464
|
-
*/
|
|
1465
|
-
async handleAddSource(args) {
|
|
1466
|
-
log.info(`🔧 [TOOL] add_source called`);
|
|
1467
|
-
log.info(` Source type: ${args.source?.type}`);
|
|
1468
|
-
try {
|
|
1469
|
-
// Validate source
|
|
1470
|
-
if (!args.source || !args.source.type || !args.source.value) {
|
|
1471
|
-
throw new Error("Source with type and value is required");
|
|
1472
|
-
}
|
|
1473
|
-
if (!["url", "text", "file"].includes(args.source.type)) {
|
|
1474
|
-
throw new Error(`Invalid source type: ${args.source.type}. Must be url, text, or file.`);
|
|
1475
|
-
}
|
|
1476
|
-
// Resolve notebook URL
|
|
1477
|
-
let notebookUrl = args.notebook_url;
|
|
1478
|
-
if (!notebookUrl && args.notebook_id) {
|
|
1479
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
1480
|
-
if (!notebook) {
|
|
1481
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
1482
|
-
}
|
|
1483
|
-
notebookUrl = notebook.url;
|
|
1484
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
1485
|
-
}
|
|
1486
|
-
else if (!notebookUrl) {
|
|
1487
|
-
const active = this.library.getActiveNotebook();
|
|
1488
|
-
if (active) {
|
|
1489
|
-
notebookUrl = active.url;
|
|
1490
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
1491
|
-
}
|
|
1492
|
-
else {
|
|
1493
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
// Validate URL
|
|
1497
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
1498
|
-
// Get the shared context manager from session manager
|
|
1499
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1500
|
-
// Add source
|
|
1501
|
-
const sourceManager = new SourceManager(this.authManager, contextManager);
|
|
1502
|
-
const result = await sourceManager.addSource(safeUrl, args.source);
|
|
1503
|
-
if (result.success) {
|
|
1504
|
-
log.success(`✅ [TOOL] add_source completed`);
|
|
1505
|
-
}
|
|
1506
|
-
else {
|
|
1507
|
-
log.warning(`⚠️ [TOOL] add_source failed: ${result.error}`);
|
|
1508
|
-
}
|
|
1509
|
-
return {
|
|
1510
|
-
success: result.success,
|
|
1511
|
-
data: result,
|
|
1512
|
-
...(result.error && { error: result.error }),
|
|
1513
|
-
};
|
|
1514
|
-
}
|
|
1515
|
-
catch (error) {
|
|
1516
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1517
|
-
log.error(`❌ [TOOL] add_source failed: ${errorMessage}`);
|
|
1518
|
-
return {
|
|
1519
|
-
success: false,
|
|
1520
|
-
error: errorMessage,
|
|
1521
|
-
};
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
/**
|
|
1525
|
-
* Handle add_folder tool
|
|
1526
|
-
*
|
|
1527
|
-
* Scans a local directory for supported files and adds them all as sources
|
|
1528
|
-
* to a NotebookLM notebook. Auto-splits into multiple notebooks if the file
|
|
1529
|
-
* count exceeds the tier's sourcesPerNotebook limit.
|
|
1530
|
-
*/
|
|
1531
|
-
async handleAddFolder(args, sendProgress) {
|
|
1532
|
-
const { promises: fs } = await import("fs");
|
|
1533
|
-
const path = await import("path");
|
|
1534
|
-
const folderPath = args.folder_path;
|
|
1535
|
-
const recursive = args.recursive ?? false;
|
|
1536
|
-
const dryRun = args.dry_run ?? false;
|
|
1537
|
-
const fileTypes = (args.file_types ?? [".pdf", ".txt", ".md", ".docx"]).map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
|
|
1538
|
-
log.info(`🔧 [TOOL] add_folder called`);
|
|
1539
|
-
log.info(` Folder: ${folderPath}`);
|
|
1540
|
-
log.info(` File types: ${fileTypes.join(", ")}`);
|
|
1541
|
-
log.info(` Recursive: ${recursive}`);
|
|
1542
|
-
log.info(` Dry run: ${dryRun}`);
|
|
1543
|
-
try {
|
|
1544
|
-
// ── 1. Validate folder ───────────────────────────────────────────────
|
|
1545
|
-
let stat;
|
|
1546
|
-
try {
|
|
1547
|
-
stat = await fs.stat(folderPath);
|
|
1548
|
-
}
|
|
1549
|
-
catch {
|
|
1550
|
-
throw new Error(`Folder not found: ${folderPath}`);
|
|
1551
|
-
}
|
|
1552
|
-
if (!stat.isDirectory()) {
|
|
1553
|
-
throw new Error(`Path is not a directory: ${folderPath}`);
|
|
1554
|
-
}
|
|
1555
|
-
// ── 2. Scan files ────────────────────────────────────────────────────
|
|
1556
|
-
const scanDir = async (dir) => {
|
|
1557
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1558
|
-
const files = [];
|
|
1559
|
-
for (const entry of entries) {
|
|
1560
|
-
const fullPath = path.join(dir, entry.name);
|
|
1561
|
-
if (entry.isDirectory() && recursive) {
|
|
1562
|
-
files.push(...(await scanDir(fullPath)));
|
|
1563
|
-
}
|
|
1564
|
-
else if (entry.isFile()) {
|
|
1565
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
1566
|
-
if (fileTypes.includes(ext)) {
|
|
1567
|
-
files.push(fullPath);
|
|
1568
|
-
}
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
return files.sort();
|
|
1572
|
-
};
|
|
1573
|
-
await sendProgress?.("Scanning folder...", 0, 10);
|
|
1574
|
-
const allFiles = await scanDir(folderPath);
|
|
1575
|
-
if (allFiles.length === 0) {
|
|
1576
|
-
return {
|
|
1577
|
-
success: true,
|
|
1578
|
-
data: {
|
|
1579
|
-
files_found: 0,
|
|
1580
|
-
files_added: 0,
|
|
1581
|
-
files_failed: 0,
|
|
1582
|
-
files_skipped: 0,
|
|
1583
|
-
notebooks_used: [],
|
|
1584
|
-
failed_files: [],
|
|
1585
|
-
dry_run: dryRun,
|
|
1586
|
-
},
|
|
1587
|
-
error: `No supported files found in ${folderPath} (looking for: ${fileTypes.join(", ")})`,
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
log.info(` Found ${allFiles.length} files`);
|
|
1591
|
-
await sendProgress?.(`Found ${allFiles.length} files`, 1, 10);
|
|
1592
|
-
// ── 3. Dry run — return preview ──────────────────────────────────────
|
|
1593
|
-
if (dryRun) {
|
|
1594
|
-
return {
|
|
1595
|
-
success: true,
|
|
1596
|
-
data: {
|
|
1597
|
-
files_found: allFiles.length,
|
|
1598
|
-
files_added: 0,
|
|
1599
|
-
files_failed: 0,
|
|
1600
|
-
files_skipped: 0,
|
|
1601
|
-
notebooks_used: [],
|
|
1602
|
-
failed_files: [],
|
|
1603
|
-
dry_run: true,
|
|
1604
|
-
},
|
|
1605
|
-
};
|
|
1606
|
-
}
|
|
1607
|
-
// ── 4. Resolve target notebook ───────────────────────────────────────
|
|
1608
|
-
let notebookUrl = args.notebook_url;
|
|
1609
|
-
let notebookName = args.notebook_name_prefix ?? path.basename(folderPath);
|
|
1610
|
-
if (!notebookUrl && args.notebook_id) {
|
|
1611
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
1612
|
-
if (!notebook)
|
|
1613
|
-
throw new Error(`Notebook not found: ${args.notebook_id}`);
|
|
1614
|
-
notebookUrl = notebook.url;
|
|
1615
|
-
notebookName = notebook.name;
|
|
1616
|
-
log.info(` Target notebook: ${notebookName}`);
|
|
1617
|
-
}
|
|
1618
|
-
else if (!notebookUrl) {
|
|
1619
|
-
const active = this.library.getActiveNotebook();
|
|
1620
|
-
if (active) {
|
|
1621
|
-
notebookUrl = active.url;
|
|
1622
|
-
notebookName = active.name;
|
|
1623
|
-
log.info(` Using active notebook: ${notebookName}`);
|
|
1624
|
-
}
|
|
1625
|
-
else {
|
|
1626
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
// ── 5. Check tier limit and chunk files ──────────────────────────────
|
|
1630
|
-
const limits = getQuotaManager().getLimits();
|
|
1631
|
-
const chunkSize = limits.sourcesPerNotebook;
|
|
1632
|
-
const chunks = [];
|
|
1633
|
-
for (let i = 0; i < allFiles.length; i += chunkSize) {
|
|
1634
|
-
chunks.push(allFiles.slice(i, i + chunkSize));
|
|
1635
|
-
}
|
|
1636
|
-
log.info(` Tier limit: ${chunkSize} sources/notebook → ${chunks.length} chunk(s)`);
|
|
1637
|
-
// ── 6. Add files ─────────────────────────────────────────────────────
|
|
1638
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1639
|
-
const notebooksUsed = [];
|
|
1640
|
-
const failedFiles = [];
|
|
1641
|
-
let totalAdded = 0;
|
|
1642
|
-
for (let chunkIdx = 0; chunkIdx < chunks.length; chunkIdx++) {
|
|
1643
|
-
const chunk = chunks[chunkIdx];
|
|
1644
|
-
// Determine notebook URL for this chunk
|
|
1645
|
-
let targetUrl;
|
|
1646
|
-
if (chunkIdx === 0) {
|
|
1647
|
-
targetUrl = validateNotebookUrl(notebookUrl);
|
|
1648
|
-
}
|
|
1649
|
-
else {
|
|
1650
|
-
// Auto-create overflow notebook
|
|
1651
|
-
const overflowName = chunks.length === 2
|
|
1652
|
-
? `${notebookName} (2/2)`
|
|
1653
|
-
: `${notebookName} (${chunkIdx + 1}/${chunks.length})`;
|
|
1654
|
-
await sendProgress?.(`Creating overflow notebook: ${overflowName}`, 2, 10);
|
|
1655
|
-
log.info(` Creating overflow notebook: ${overflowName}`);
|
|
1656
|
-
const created = await this.handleCreateNotebook({ name: overflowName, sources: [], auto_add_to_library: true }, sendProgress);
|
|
1657
|
-
if (!created.success || !created.data?.url) {
|
|
1658
|
-
failedFiles.push(...chunk.map((f) => ({ file: f, error: `Could not create overflow notebook ${overflowName}` })));
|
|
1659
|
-
continue;
|
|
1660
|
-
}
|
|
1661
|
-
targetUrl = validateNotebookUrl(created.data.url);
|
|
1662
|
-
notebooksUsed.push(overflowName);
|
|
1663
|
-
}
|
|
1664
|
-
if (chunkIdx === 0) {
|
|
1665
|
-
notebooksUsed.unshift(notebookName);
|
|
1666
|
-
}
|
|
1667
|
-
const sourceManager = new SourceManager(this.authManager, contextManager);
|
|
1668
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
1669
|
-
const filePath = chunk[i];
|
|
1670
|
-
const fileName = path.basename(filePath);
|
|
1671
|
-
const globalIdx = chunkIdx * chunkSize + i + 1;
|
|
1672
|
-
const progressStep = Math.min(9, 2 + Math.floor((globalIdx / allFiles.length) * 7));
|
|
1673
|
-
await sendProgress?.(`Adding file ${globalIdx}/${allFiles.length}: ${fileName}`, progressStep, 10);
|
|
1674
|
-
log.info(` [${globalIdx}/${allFiles.length}] Adding: ${fileName}`);
|
|
1675
|
-
try {
|
|
1676
|
-
const result = await sourceManager.addSource(targetUrl, {
|
|
1677
|
-
type: "file",
|
|
1678
|
-
value: filePath,
|
|
1679
|
-
});
|
|
1680
|
-
if (result.success) {
|
|
1681
|
-
totalAdded++;
|
|
1682
|
-
}
|
|
1683
|
-
else {
|
|
1684
|
-
failedFiles.push({ file: filePath, error: result.error ?? "Unknown error" });
|
|
1685
|
-
log.warning(` ⚠️ Failed: ${fileName} — ${result.error}`);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
catch (err) {
|
|
1689
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1690
|
-
failedFiles.push({ file: filePath, error: msg });
|
|
1691
|
-
log.warning(` ⚠️ Error: ${fileName} — ${msg}`);
|
|
1692
|
-
}
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
await sendProgress?.("Done!", 10, 10);
|
|
1696
|
-
log.success(`✅ [TOOL] add_folder complete: ${totalAdded}/${allFiles.length} added, ${failedFiles.length} failed`);
|
|
1697
|
-
return {
|
|
1698
|
-
success: failedFiles.length === 0,
|
|
1699
|
-
data: {
|
|
1700
|
-
files_found: allFiles.length,
|
|
1701
|
-
files_added: totalAdded,
|
|
1702
|
-
files_failed: failedFiles.length,
|
|
1703
|
-
files_skipped: allFiles.length - totalAdded - failedFiles.length,
|
|
1704
|
-
notebooks_used: notebooksUsed,
|
|
1705
|
-
failed_files: failedFiles,
|
|
1706
|
-
dry_run: false,
|
|
1707
|
-
},
|
|
1708
|
-
...(failedFiles.length > 0 && {
|
|
1709
|
-
error: `${failedFiles.length} file(s) failed to add`,
|
|
1710
|
-
}),
|
|
1711
|
-
};
|
|
1712
|
-
}
|
|
1713
|
-
catch (error) {
|
|
1714
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1715
|
-
log.error(`❌ [TOOL] add_folder failed: ${errorMessage}`);
|
|
1716
|
-
return { success: false, error: errorMessage };
|
|
1717
|
-
}
|
|
1718
|
-
}
|
|
1719
|
-
/**
|
|
1720
|
-
* Handle remove_source tool
|
|
1721
|
-
*
|
|
1722
|
-
* Remove a source from a NotebookLM notebook.
|
|
1723
|
-
*/
|
|
1724
|
-
async handleRemoveSource(args) {
|
|
1725
|
-
log.info(`🔧 [TOOL] remove_source called`);
|
|
1726
|
-
log.info(` Source ID: ${args.source_id}`);
|
|
1727
|
-
try {
|
|
1728
|
-
// Validate source_id
|
|
1729
|
-
if (!args.source_id) {
|
|
1730
|
-
throw new Error("source_id is required");
|
|
1731
|
-
}
|
|
1732
|
-
// Resolve notebook URL
|
|
1733
|
-
let notebookUrl = args.notebook_url;
|
|
1734
|
-
if (!notebookUrl && args.notebook_id) {
|
|
1735
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
1736
|
-
if (!notebook) {
|
|
1737
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
1738
|
-
}
|
|
1739
|
-
notebookUrl = notebook.url;
|
|
1740
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
1741
|
-
}
|
|
1742
|
-
else if (!notebookUrl) {
|
|
1743
|
-
const active = this.library.getActiveNotebook();
|
|
1744
|
-
if (active) {
|
|
1745
|
-
notebookUrl = active.url;
|
|
1746
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
1747
|
-
}
|
|
1748
|
-
else {
|
|
1749
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
1750
|
-
}
|
|
1751
|
-
}
|
|
1752
|
-
// Validate URL
|
|
1753
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
1754
|
-
// Get the shared context manager from session manager
|
|
1755
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1756
|
-
// Remove source
|
|
1757
|
-
const sourceManager = new SourceManager(this.authManager, contextManager);
|
|
1758
|
-
const result = await sourceManager.removeSource(safeUrl, args.source_id);
|
|
1759
|
-
if (result.success) {
|
|
1760
|
-
log.success(`✅ [TOOL] remove_source completed`);
|
|
1761
|
-
}
|
|
1762
|
-
else {
|
|
1763
|
-
log.warning(`⚠️ [TOOL] remove_source failed: ${result.error}`);
|
|
1764
|
-
}
|
|
1765
|
-
return {
|
|
1766
|
-
success: result.success,
|
|
1767
|
-
data: result,
|
|
1768
|
-
...(result.error && { error: result.error }),
|
|
1769
|
-
};
|
|
1770
|
-
}
|
|
1771
|
-
catch (error) {
|
|
1772
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1773
|
-
log.error(`❌ [TOOL] remove_source failed: ${errorMessage}`);
|
|
1774
|
-
return {
|
|
1775
|
-
success: false,
|
|
1776
|
-
error: errorMessage,
|
|
1777
|
-
};
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
/**
|
|
1781
|
-
* Handle generate_audio_overview tool
|
|
1782
|
-
*
|
|
1783
|
-
* Triggers audio overview generation for a notebook.
|
|
1784
|
-
*/
|
|
1785
|
-
async handleGenerateAudioOverview(args) {
|
|
1786
|
-
log.info(`🔧 [TOOL] generate_audio_overview called`);
|
|
1787
|
-
try {
|
|
1788
|
-
// Resolve notebook URL
|
|
1789
|
-
let notebookUrl = args.notebook_url;
|
|
1790
|
-
if (!notebookUrl && args.notebook_id) {
|
|
1791
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
1792
|
-
if (!notebook) {
|
|
1793
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
1794
|
-
}
|
|
1795
|
-
notebookUrl = notebook.url;
|
|
1796
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
1797
|
-
}
|
|
1798
|
-
else if (!notebookUrl) {
|
|
1799
|
-
const active = this.library.getActiveNotebook();
|
|
1800
|
-
if (active) {
|
|
1801
|
-
notebookUrl = active.url;
|
|
1802
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
1803
|
-
}
|
|
1804
|
-
else {
|
|
1805
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
// Validate URL
|
|
1809
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
1810
|
-
// Get the shared context manager from session manager
|
|
1811
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1812
|
-
// Generate audio
|
|
1813
|
-
const audioManager = new AudioManager(this.authManager, contextManager);
|
|
1814
|
-
const result = await audioManager.generateAudioOverview(safeUrl);
|
|
1815
|
-
if (result.success) {
|
|
1816
|
-
log.success(`✅ [TOOL] generate_audio_overview completed (status: ${result.status.status})`);
|
|
1817
|
-
}
|
|
1818
|
-
else {
|
|
1819
|
-
log.warning(`⚠️ [TOOL] generate_audio_overview: ${result.error}`);
|
|
1820
|
-
}
|
|
1821
|
-
return {
|
|
1822
|
-
success: result.success,
|
|
1823
|
-
data: result,
|
|
1824
|
-
...(result.error && { error: result.error }),
|
|
1825
|
-
};
|
|
1826
|
-
}
|
|
1827
|
-
catch (error) {
|
|
1828
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1829
|
-
log.error(`❌ [TOOL] generate_audio_overview failed: ${errorMessage}`);
|
|
1830
|
-
return {
|
|
1831
|
-
success: false,
|
|
1832
|
-
error: errorMessage,
|
|
1833
|
-
};
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
/**
|
|
1837
|
-
* Handle get_audio_status tool
|
|
1838
|
-
*
|
|
1839
|
-
* Checks the audio generation status for a notebook.
|
|
1840
|
-
*/
|
|
1841
|
-
async handleGetAudioStatus(args) {
|
|
1842
|
-
log.info(`🔧 [TOOL] get_audio_status called`);
|
|
1843
|
-
try {
|
|
1844
|
-
// Resolve notebook URL
|
|
1845
|
-
let notebookUrl = args.notebook_url;
|
|
1846
|
-
if (!notebookUrl && args.notebook_id) {
|
|
1847
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
1848
|
-
if (!notebook) {
|
|
1849
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
1850
|
-
}
|
|
1851
|
-
notebookUrl = notebook.url;
|
|
1852
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
1853
|
-
}
|
|
1854
|
-
else if (!notebookUrl) {
|
|
1855
|
-
const active = this.library.getActiveNotebook();
|
|
1856
|
-
if (active) {
|
|
1857
|
-
notebookUrl = active.url;
|
|
1858
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
1859
|
-
}
|
|
1860
|
-
else {
|
|
1861
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
// Validate URL
|
|
1865
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
1866
|
-
// Get the shared context manager from session manager
|
|
1867
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1868
|
-
// Get status
|
|
1869
|
-
const audioManager = new AudioManager(this.authManager, contextManager);
|
|
1870
|
-
const status = await audioManager.getAudioStatus(safeUrl);
|
|
1871
|
-
log.success(`✅ [TOOL] get_audio_status completed (status: ${status.status})`);
|
|
1872
|
-
return {
|
|
1873
|
-
success: true,
|
|
1874
|
-
data: status,
|
|
1875
|
-
};
|
|
1876
|
-
}
|
|
1877
|
-
catch (error) {
|
|
1878
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1879
|
-
log.error(`❌ [TOOL] get_audio_status failed: ${errorMessage}`);
|
|
1880
|
-
return {
|
|
1881
|
-
success: false,
|
|
1882
|
-
error: errorMessage,
|
|
1883
|
-
};
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
/**
|
|
1887
|
-
* Handle download_audio tool
|
|
1888
|
-
*
|
|
1889
|
-
* Downloads the generated audio file.
|
|
1890
|
-
*/
|
|
1891
|
-
async handleDownloadAudio(args) {
|
|
1892
|
-
log.info(`🔧 [TOOL] download_audio called`);
|
|
1893
|
-
try {
|
|
1894
|
-
// Resolve notebook URL
|
|
1895
|
-
let notebookUrl = args.notebook_url;
|
|
1896
|
-
if (!notebookUrl && args.notebook_id) {
|
|
1897
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
1898
|
-
if (!notebook) {
|
|
1899
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
1900
|
-
}
|
|
1901
|
-
notebookUrl = notebook.url;
|
|
1902
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
1903
|
-
}
|
|
1904
|
-
else if (!notebookUrl) {
|
|
1905
|
-
const active = this.library.getActiveNotebook();
|
|
1906
|
-
if (active) {
|
|
1907
|
-
notebookUrl = active.url;
|
|
1908
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
1909
|
-
}
|
|
1910
|
-
else {
|
|
1911
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
// Validate URL
|
|
1915
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
1916
|
-
// Get the shared context manager from session manager
|
|
1917
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
1918
|
-
// Download audio
|
|
1919
|
-
const audioManager = new AudioManager(this.authManager, contextManager);
|
|
1920
|
-
const result = await audioManager.downloadAudio(safeUrl, args.output_path);
|
|
1921
|
-
if (result.success) {
|
|
1922
|
-
log.success(`✅ [TOOL] download_audio completed: ${result.filePath}`);
|
|
1923
|
-
}
|
|
1924
|
-
else {
|
|
1925
|
-
log.warning(`⚠️ [TOOL] download_audio: ${result.error}`);
|
|
1926
|
-
}
|
|
1927
|
-
return {
|
|
1928
|
-
success: result.success,
|
|
1929
|
-
data: result,
|
|
1930
|
-
...(result.error && { error: result.error }),
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
|
-
catch (error) {
|
|
1934
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1935
|
-
log.error(`❌ [TOOL] download_audio failed: ${errorMessage}`);
|
|
1936
|
-
return {
|
|
1937
|
-
success: false,
|
|
1938
|
-
error: errorMessage,
|
|
1939
|
-
};
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
/**
|
|
1943
|
-
* Handle configure_webhook tool
|
|
1944
|
-
*
|
|
1945
|
-
* Add or update a webhook endpoint.
|
|
1946
|
-
*/
|
|
1947
|
-
async handleConfigureWebhook(args) {
|
|
1948
|
-
log.info(`🔧 [TOOL] configure_webhook called`);
|
|
1949
|
-
log.info(` Name: ${args.name}`);
|
|
1950
|
-
try {
|
|
1951
|
-
const dispatcher = getWebhookDispatcher();
|
|
1952
|
-
if (args.id) {
|
|
1953
|
-
// Update existing
|
|
1954
|
-
const updated = dispatcher.updateWebhook({
|
|
1955
|
-
id: args.id,
|
|
1956
|
-
name: args.name,
|
|
1957
|
-
url: args.url,
|
|
1958
|
-
enabled: args.enabled,
|
|
1959
|
-
events: args.events,
|
|
1960
|
-
format: args.format,
|
|
1961
|
-
secret: args.secret,
|
|
1962
|
-
});
|
|
1963
|
-
if (!updated) {
|
|
1964
|
-
throw new Error(`Webhook not found: ${args.id}`);
|
|
1965
|
-
}
|
|
1966
|
-
log.success(`✅ [TOOL] configure_webhook updated: ${updated.name}`);
|
|
1967
|
-
return { success: true, data: updated };
|
|
1968
|
-
}
|
|
1969
|
-
else {
|
|
1970
|
-
// Create new
|
|
1971
|
-
const webhook = dispatcher.addWebhook({
|
|
1972
|
-
name: args.name,
|
|
1973
|
-
url: args.url,
|
|
1974
|
-
events: args.events,
|
|
1975
|
-
format: args.format,
|
|
1976
|
-
secret: args.secret,
|
|
1977
|
-
});
|
|
1978
|
-
log.success(`✅ [TOOL] configure_webhook created: ${webhook.name}`);
|
|
1979
|
-
return { success: true, data: webhook };
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
catch (error) {
|
|
1983
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1984
|
-
log.error(`❌ [TOOL] configure_webhook failed: ${errorMessage}`);
|
|
1985
|
-
return { success: false, error: errorMessage };
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Handle list_webhooks tool
|
|
1990
|
-
*
|
|
1991
|
-
* List all configured webhooks.
|
|
1992
|
-
*/
|
|
1993
|
-
async handleListWebhooks() {
|
|
1994
|
-
log.info(`🔧 [TOOL] list_webhooks called`);
|
|
1995
|
-
try {
|
|
1996
|
-
const dispatcher = getWebhookDispatcher();
|
|
1997
|
-
const webhooks = dispatcher.listWebhooks();
|
|
1998
|
-
const stats = dispatcher.getStats();
|
|
1999
|
-
log.success(`✅ [TOOL] list_webhooks completed (${webhooks.length} webhooks)`);
|
|
2000
|
-
return {
|
|
2001
|
-
success: true,
|
|
2002
|
-
data: { webhooks, stats },
|
|
2003
|
-
};
|
|
2004
|
-
}
|
|
2005
|
-
catch (error) {
|
|
2006
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2007
|
-
log.error(`❌ [TOOL] list_webhooks failed: ${errorMessage}`);
|
|
2008
|
-
return { success: false, error: errorMessage };
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
/**
|
|
2012
|
-
* Handle test_webhook tool
|
|
2013
|
-
*
|
|
2014
|
-
* Send a test event to a webhook.
|
|
2015
|
-
*/
|
|
2016
|
-
async handleTestWebhook(args) {
|
|
2017
|
-
log.info(`🔧 [TOOL] test_webhook called`);
|
|
2018
|
-
log.info(` ID: ${args.id}`);
|
|
2019
|
-
try {
|
|
2020
|
-
const dispatcher = getWebhookDispatcher();
|
|
2021
|
-
const result = await dispatcher.testWebhook(args.id);
|
|
2022
|
-
if (result.success) {
|
|
2023
|
-
log.success(`✅ [TOOL] test_webhook succeeded`);
|
|
2024
|
-
return {
|
|
2025
|
-
success: true,
|
|
2026
|
-
data: { success: true, message: "Test event delivered successfully" },
|
|
2027
|
-
};
|
|
2028
|
-
}
|
|
2029
|
-
else {
|
|
2030
|
-
log.warning(`⚠️ [TOOL] test_webhook failed: ${result.error}`);
|
|
2031
|
-
return {
|
|
2032
|
-
success: false,
|
|
2033
|
-
data: { success: false, message: result.error || "Test failed" },
|
|
2034
|
-
error: result.error,
|
|
2035
|
-
};
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
catch (error) {
|
|
2039
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2040
|
-
log.error(`❌ [TOOL] test_webhook failed: ${errorMessage}`);
|
|
2041
|
-
return { success: false, error: errorMessage };
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
/**
|
|
2045
|
-
* Handle remove_webhook tool
|
|
2046
|
-
*
|
|
2047
|
-
* Remove a configured webhook.
|
|
2048
|
-
*/
|
|
2049
|
-
async handleRemoveWebhook(args) {
|
|
2050
|
-
log.info(`🔧 [TOOL] remove_webhook called`);
|
|
2051
|
-
log.info(` ID: ${args.id}`);
|
|
2052
|
-
try {
|
|
2053
|
-
const dispatcher = getWebhookDispatcher();
|
|
2054
|
-
const removed = dispatcher.removeWebhook(args.id);
|
|
2055
|
-
if (removed) {
|
|
2056
|
-
log.success(`✅ [TOOL] remove_webhook completed`);
|
|
2057
|
-
return {
|
|
2058
|
-
success: true,
|
|
2059
|
-
data: { removed: true, id: args.id },
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
|
-
else {
|
|
2063
|
-
log.warning(`⚠️ [TOOL] Webhook not found: ${args.id}`);
|
|
2064
|
-
return {
|
|
2065
|
-
success: false,
|
|
2066
|
-
error: `Webhook not found: ${args.id}`,
|
|
2067
|
-
};
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
catch (error) {
|
|
2071
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2072
|
-
log.error(`❌ [TOOL] remove_webhook failed: ${errorMessage}`);
|
|
2073
|
-
return { success: false, error: errorMessage };
|
|
2074
|
-
}
|
|
2075
|
-
}
|
|
2076
|
-
// ==================== GEMINI API HANDLERS ====================
|
|
2077
|
-
/**
|
|
2078
|
-
* Handle deep_research tool
|
|
2079
|
-
*
|
|
2080
|
-
* Performs comprehensive research using Gemini's Deep Research agent.
|
|
2081
|
-
*/
|
|
2082
|
-
async handleDeepResearch(args, sendProgress) {
|
|
2083
|
-
const startTime = Date.now();
|
|
2084
|
-
log.info(`🔧 [TOOL] deep_research called`);
|
|
2085
|
-
log.info(` Query: "${sanitizeForLogging(args.query.substring(0, 100))}"...`);
|
|
2086
|
-
// Check if Gemini is available
|
|
2087
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2088
|
-
log.error(`❌ [TOOL] deep_research failed: Gemini API key not configured`);
|
|
2089
|
-
return {
|
|
2090
|
-
success: false,
|
|
2091
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2092
|
-
};
|
|
2093
|
-
}
|
|
2094
|
-
try {
|
|
2095
|
-
// Validate query
|
|
2096
|
-
if (!args.query || args.query.trim().length === 0) {
|
|
2097
|
-
throw new Error("Query cannot be empty");
|
|
2098
|
-
}
|
|
2099
|
-
if (args.query.length > 10000) {
|
|
2100
|
-
throw new Error("Query too long (max 10000 characters)");
|
|
2101
|
-
}
|
|
2102
|
-
// Validate max_wait_seconds
|
|
2103
|
-
const maxWaitSeconds = Math.min(args.max_wait_seconds || 300, 600); // Max 10 minutes
|
|
2104
|
-
const maxWaitMs = maxWaitSeconds * 1000;
|
|
2105
|
-
if (sendProgress) {
|
|
2106
|
-
await sendProgress("Starting deep research...", 0, 100);
|
|
2107
|
-
}
|
|
2108
|
-
// Start the research
|
|
2109
|
-
const interaction = await this.geminiClient.deepResearch({
|
|
2110
|
-
query: args.query,
|
|
2111
|
-
background: true,
|
|
2112
|
-
waitForCompletion: args.wait_for_completion !== false,
|
|
2113
|
-
maxWaitMs,
|
|
2114
|
-
progressCallback: sendProgress,
|
|
2115
|
-
});
|
|
2116
|
-
const durationMs = Date.now() - startTime;
|
|
2117
|
-
// Extract the answer
|
|
2118
|
-
const answer = interaction.outputs.find(o => o.type === "text")?.text || "";
|
|
2119
|
-
// Audit log
|
|
2120
|
-
await audit.tool("deep_research", { query: sanitizeForLogging(args.query) }, true, durationMs);
|
|
2121
|
-
log.success(`✅ [TOOL] deep_research completed in ${durationMs}ms`);
|
|
2122
|
-
return {
|
|
2123
|
-
success: true,
|
|
2124
|
-
data: {
|
|
2125
|
-
interactionId: interaction.id,
|
|
2126
|
-
status: interaction.status,
|
|
2127
|
-
answer,
|
|
2128
|
-
tokensUsed: interaction.usage?.totalTokens,
|
|
2129
|
-
durationMs,
|
|
2130
|
-
...(interaction.deprecationWarning && { deprecationWarning: interaction.deprecationWarning }),
|
|
2131
|
-
},
|
|
2132
|
-
};
|
|
2133
|
-
}
|
|
2134
|
-
catch (error) {
|
|
2135
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2136
|
-
const durationMs = Date.now() - startTime;
|
|
2137
|
-
log.error(`❌ [TOOL] deep_research failed: ${errorMessage}`);
|
|
2138
|
-
await audit.tool("deep_research", { query: sanitizeForLogging(args.query) }, false, durationMs, errorMessage);
|
|
2139
|
-
return { success: false, error: errorMessage };
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
/**
|
|
2143
|
-
* Handle gemini_query tool
|
|
2144
|
-
*
|
|
2145
|
-
* Quick query to Gemini model with optional grounding tools.
|
|
2146
|
-
*/
|
|
2147
|
-
async handleGeminiQuery(args) {
|
|
2148
|
-
const startTime = Date.now();
|
|
2149
|
-
log.info(`🔧 [TOOL] gemini_query called`);
|
|
2150
|
-
log.info(` Query: "${sanitizeForLogging(args.query.substring(0, 100))}"...`);
|
|
2151
|
-
log.info(` Model: ${args.model || "default"}`);
|
|
2152
|
-
if (args.tools)
|
|
2153
|
-
log.info(` Tools: ${args.tools.join(", ")}`);
|
|
2154
|
-
// Check if Gemini is available
|
|
2155
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2156
|
-
log.error(`❌ [TOOL] gemini_query failed: Gemini API key not configured`);
|
|
2157
|
-
return {
|
|
2158
|
-
success: false,
|
|
2159
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2160
|
-
};
|
|
2161
|
-
}
|
|
2162
|
-
try {
|
|
2163
|
-
// Validate query
|
|
2164
|
-
if (!args.query || args.query.trim().length === 0) {
|
|
2165
|
-
throw new Error("Query cannot be empty");
|
|
2166
|
-
}
|
|
2167
|
-
if (args.query.length > 30000) {
|
|
2168
|
-
throw new Error("Query too long (max 30000 characters)");
|
|
2169
|
-
}
|
|
2170
|
-
// If URLs provided, auto-enable url_context
|
|
2171
|
-
let tools = args.tools || [];
|
|
2172
|
-
if (args.urls && args.urls.length > 0 && !tools.includes("url_context")) {
|
|
2173
|
-
tools = [...tools, "url_context"];
|
|
2174
|
-
}
|
|
2175
|
-
// Validate URLs if provided
|
|
2176
|
-
if (args.urls) {
|
|
2177
|
-
for (const url of args.urls) {
|
|
2178
|
-
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
2179
|
-
throw new Error(`Invalid URL: ${url} (must start with http:// or https://)`);
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
// Build generationConfig from thinking_level and response_schema
|
|
2184
|
-
const hasGenConfig = args.thinking_level || args.response_schema;
|
|
2185
|
-
const generationConfig = hasGenConfig ? {
|
|
2186
|
-
...(args.thinking_level && { thinkingLevel: args.thinking_level }),
|
|
2187
|
-
...(args.response_schema && {
|
|
2188
|
-
responseMimeType: "application/json",
|
|
2189
|
-
responseSchema: args.response_schema,
|
|
2190
|
-
}),
|
|
2191
|
-
} : undefined;
|
|
2192
|
-
const interaction = await this.geminiClient.query({
|
|
2193
|
-
query: args.query,
|
|
2194
|
-
model: args.model,
|
|
2195
|
-
tools,
|
|
2196
|
-
urls: args.urls,
|
|
2197
|
-
previousInteractionId: args.previous_interaction_id,
|
|
2198
|
-
generationConfig,
|
|
2199
|
-
});
|
|
2200
|
-
const durationMs = Date.now() - startTime;
|
|
2201
|
-
// Extract the answer
|
|
2202
|
-
const answer = interaction.outputs.find(o => o.type === "text")?.text || "";
|
|
2203
|
-
// Identify which tools were used
|
|
2204
|
-
const toolsUsed = interaction.outputs
|
|
2205
|
-
.filter(o => o.type === "function_call")
|
|
2206
|
-
.map(o => o.name)
|
|
2207
|
-
.filter((name) => !!name);
|
|
2208
|
-
// Audit log
|
|
2209
|
-
await audit.tool("gemini_query", {
|
|
2210
|
-
query: sanitizeForLogging(args.query),
|
|
2211
|
-
model: args.model,
|
|
2212
|
-
tools: args.tools,
|
|
2213
|
-
}, true, durationMs);
|
|
2214
|
-
log.success(`✅ [TOOL] gemini_query completed in ${durationMs}ms`);
|
|
2215
|
-
return {
|
|
2216
|
-
success: true,
|
|
2217
|
-
data: {
|
|
2218
|
-
interactionId: interaction.id,
|
|
2219
|
-
answer,
|
|
2220
|
-
model: interaction.model || args.model || CONFIG.geminiDefaultModel,
|
|
2221
|
-
tokensUsed: interaction.usage?.totalTokens,
|
|
2222
|
-
toolsUsed: toolsUsed.length > 0 ? toolsUsed : undefined,
|
|
2223
|
-
...(interaction.deprecationWarning && { deprecationWarning: interaction.deprecationWarning }),
|
|
2224
|
-
},
|
|
2225
|
-
};
|
|
2226
|
-
}
|
|
2227
|
-
catch (error) {
|
|
2228
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2229
|
-
const durationMs = Date.now() - startTime;
|
|
2230
|
-
log.error(`❌ [TOOL] gemini_query failed: ${errorMessage}`);
|
|
2231
|
-
await audit.tool("gemini_query", { query: sanitizeForLogging(args.query) }, false, durationMs, errorMessage);
|
|
2232
|
-
return { success: false, error: errorMessage };
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
/**
|
|
2236
|
-
* Handle get_research_status tool
|
|
2237
|
-
*
|
|
2238
|
-
* Check the status of a background deep research task.
|
|
2239
|
-
*/
|
|
2240
|
-
async handleGetResearchStatus(args) {
|
|
2241
|
-
log.info(`🔧 [TOOL] get_research_status called`);
|
|
2242
|
-
log.info(` Interaction ID: ${args.interaction_id}`);
|
|
2243
|
-
// Check if Gemini is available
|
|
2244
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2245
|
-
log.error(`❌ [TOOL] get_research_status failed: Gemini API key not configured`);
|
|
2246
|
-
return {
|
|
2247
|
-
success: false,
|
|
2248
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2249
|
-
};
|
|
2250
|
-
}
|
|
2251
|
-
try {
|
|
2252
|
-
// Validate interaction_id
|
|
2253
|
-
if (!args.interaction_id || args.interaction_id.trim().length === 0) {
|
|
2254
|
-
throw new Error("Interaction ID cannot be empty");
|
|
2255
|
-
}
|
|
2256
|
-
const interaction = await this.geminiClient.getInteraction(args.interaction_id);
|
|
2257
|
-
log.success(`✅ [TOOL] get_research_status: ${interaction.status}`);
|
|
2258
|
-
return {
|
|
2259
|
-
success: true,
|
|
2260
|
-
data: interaction,
|
|
2261
|
-
};
|
|
2262
|
-
}
|
|
2263
|
-
catch (error) {
|
|
2264
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2265
|
-
log.error(`❌ [TOOL] get_research_status failed: ${errorMessage}`);
|
|
2266
|
-
return { success: false, error: errorMessage };
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
// ==================== DOCUMENT TOOLS (v1.9.0) ====================
|
|
2270
|
-
/**
|
|
2271
|
-
* Upload a document to Gemini Files API
|
|
2272
|
-
*/
|
|
2273
|
-
async handleUploadDocument(args) {
|
|
2274
|
-
const startTime = Date.now();
|
|
2275
|
-
log.info(`🔧 [TOOL] upload_document called`);
|
|
2276
|
-
log.info(` File: ${args.file_path}`);
|
|
2277
|
-
// Check if Gemini is available
|
|
2278
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2279
|
-
log.error(`❌ [TOOL] upload_document failed: Gemini API key not configured`);
|
|
2280
|
-
return {
|
|
2281
|
-
success: false,
|
|
2282
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2283
|
-
};
|
|
2284
|
-
}
|
|
2285
|
-
try {
|
|
2286
|
-
// Validate file path
|
|
2287
|
-
if (!args.file_path || args.file_path.trim().length === 0) {
|
|
2288
|
-
throw new Error("File path cannot be empty");
|
|
2289
|
-
}
|
|
2290
|
-
const result = await this.geminiClient.uploadDocument({
|
|
2291
|
-
filePath: args.file_path,
|
|
2292
|
-
displayName: args.display_name,
|
|
2293
|
-
});
|
|
2294
|
-
const durationMs = Date.now() - startTime;
|
|
2295
|
-
await audit.tool("upload_document", { file: sanitizeForLogging(args.file_path) }, true, durationMs);
|
|
2296
|
-
log.success(`✅ [TOOL] upload_document completed in ${durationMs}ms`);
|
|
2297
|
-
return {
|
|
2298
|
-
success: true,
|
|
2299
|
-
data: result,
|
|
2300
|
-
};
|
|
2301
|
-
}
|
|
2302
|
-
catch (error) {
|
|
2303
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2304
|
-
const durationMs = Date.now() - startTime;
|
|
2305
|
-
await audit.tool("upload_document", { file: sanitizeForLogging(args.file_path) }, false, durationMs, errorMessage);
|
|
2306
|
-
log.error(`❌ [TOOL] upload_document failed: ${errorMessage}`);
|
|
2307
|
-
return { success: false, error: errorMessage };
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
/**
|
|
2311
|
-
* Query an uploaded document
|
|
2312
|
-
*/
|
|
2313
|
-
async handleQueryDocument(args) {
|
|
2314
|
-
const startTime = Date.now();
|
|
2315
|
-
log.info(`🔧 [TOOL] query_document called`);
|
|
2316
|
-
log.info(` File: ${args.file_name}`);
|
|
2317
|
-
log.info(` Query: ${args.query.substring(0, 50)}...`);
|
|
2318
|
-
// Check if Gemini is available
|
|
2319
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2320
|
-
log.error(`❌ [TOOL] query_document failed: Gemini API key not configured`);
|
|
2321
|
-
return {
|
|
2322
|
-
success: false,
|
|
2323
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2324
|
-
};
|
|
2325
|
-
}
|
|
2326
|
-
try {
|
|
2327
|
-
// Validate inputs
|
|
2328
|
-
if (!args.file_name || args.file_name.trim().length === 0) {
|
|
2329
|
-
throw new Error("File name cannot be empty");
|
|
2330
|
-
}
|
|
2331
|
-
if (!args.query || args.query.trim().length === 0) {
|
|
2332
|
-
throw new Error("Query cannot be empty");
|
|
2333
|
-
}
|
|
2334
|
-
const result = await this.geminiClient.queryDocument({
|
|
2335
|
-
fileName: args.file_name,
|
|
2336
|
-
query: args.query,
|
|
2337
|
-
model: args.model,
|
|
2338
|
-
additionalFiles: args.additional_files,
|
|
2339
|
-
});
|
|
2340
|
-
const durationMs = Date.now() - startTime;
|
|
2341
|
-
await audit.tool("query_document", { file: args.file_name, query: sanitizeForLogging(args.query) }, true, durationMs);
|
|
2342
|
-
log.success(`✅ [TOOL] query_document completed in ${durationMs}ms`);
|
|
2343
|
-
return {
|
|
2344
|
-
success: true,
|
|
2345
|
-
data: result,
|
|
2346
|
-
};
|
|
2347
|
-
}
|
|
2348
|
-
catch (error) {
|
|
2349
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2350
|
-
const durationMs = Date.now() - startTime;
|
|
2351
|
-
await audit.tool("query_document", { file: args.file_name }, false, durationMs, errorMessage);
|
|
2352
|
-
log.error(`❌ [TOOL] query_document failed: ${errorMessage}`);
|
|
2353
|
-
return { success: false, error: errorMessage };
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
/**
|
|
2357
|
-
* List all uploaded documents
|
|
2358
|
-
*/
|
|
2359
|
-
async handleListDocuments(args) {
|
|
2360
|
-
log.info(`🔧 [TOOL] list_documents called`);
|
|
2361
|
-
// Check if Gemini is available
|
|
2362
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2363
|
-
log.error(`❌ [TOOL] list_documents failed: Gemini API key not configured`);
|
|
2364
|
-
return {
|
|
2365
|
-
success: false,
|
|
2366
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2367
|
-
};
|
|
2368
|
-
}
|
|
2369
|
-
try {
|
|
2370
|
-
const result = await this.geminiClient.listFiles(args.page_size || 100);
|
|
2371
|
-
log.success(`✅ [TOOL] list_documents: ${result.totalCount} files`);
|
|
2372
|
-
return {
|
|
2373
|
-
success: true,
|
|
2374
|
-
data: result,
|
|
2375
|
-
};
|
|
2376
|
-
}
|
|
2377
|
-
catch (error) {
|
|
2378
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2379
|
-
log.error(`❌ [TOOL] list_documents failed: ${errorMessage}`);
|
|
2380
|
-
return { success: false, error: errorMessage };
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
/**
|
|
2384
|
-
* Delete an uploaded document
|
|
2385
|
-
*/
|
|
2386
|
-
async handleDeleteDocument(args) {
|
|
2387
|
-
log.info(`🔧 [TOOL] delete_document called`);
|
|
2388
|
-
log.info(` File: ${args.file_name}`);
|
|
2389
|
-
// Check if Gemini is available
|
|
2390
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2391
|
-
log.error(`❌ [TOOL] delete_document failed: Gemini API key not configured`);
|
|
2392
|
-
return {
|
|
2393
|
-
success: false,
|
|
2394
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2395
|
-
};
|
|
2396
|
-
}
|
|
2397
|
-
try {
|
|
2398
|
-
// Validate file name
|
|
2399
|
-
if (!args.file_name || args.file_name.trim().length === 0) {
|
|
2400
|
-
throw new Error("File name cannot be empty");
|
|
2401
|
-
}
|
|
2402
|
-
await this.geminiClient.deleteFile(args.file_name);
|
|
2403
|
-
log.success(`✅ [TOOL] delete_document: ${args.file_name} deleted`);
|
|
2404
|
-
return {
|
|
2405
|
-
success: true,
|
|
2406
|
-
data: { deleted: true, fileName: args.file_name },
|
|
2407
|
-
};
|
|
2408
|
-
}
|
|
2409
|
-
catch (error) {
|
|
2410
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2411
|
-
log.error(`❌ [TOOL] delete_document failed: ${errorMessage}`);
|
|
2412
|
-
return { success: false, error: errorMessage };
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
/**
|
|
2416
|
-
* Query a chunked document (v1.10.0)
|
|
2417
|
-
* Queries multiple chunks and aggregates results
|
|
2418
|
-
*/
|
|
2419
|
-
async handleQueryChunkedDocument(args) {
|
|
2420
|
-
log.info(`🔧 [TOOL] query_chunked_document called`);
|
|
2421
|
-
log.info(` Chunks: ${args.file_names.length}`);
|
|
2422
|
-
log.info(` Query: ${args.query.substring(0, 50)}...`);
|
|
2423
|
-
// Check if Gemini is available
|
|
2424
|
-
if (!this.geminiClient.isAvailable()) {
|
|
2425
|
-
log.error(`❌ [TOOL] query_chunked_document failed: Gemini API key not configured`);
|
|
2426
|
-
return {
|
|
2427
|
-
success: false,
|
|
2428
|
-
error: "Gemini API key not configured. Set GEMINI_API_KEY environment variable.",
|
|
2429
|
-
};
|
|
2430
|
-
}
|
|
2431
|
-
try {
|
|
2432
|
-
// Validate inputs
|
|
2433
|
-
if (!args.file_names || args.file_names.length === 0) {
|
|
2434
|
-
throw new Error("At least one file name is required");
|
|
2435
|
-
}
|
|
2436
|
-
if (!args.query || args.query.trim().length === 0) {
|
|
2437
|
-
throw new Error("Query cannot be empty");
|
|
2438
|
-
}
|
|
2439
|
-
const result = await this.geminiClient.queryChunkedDocument(args.file_names, args.query, { model: args.model });
|
|
2440
|
-
log.success(`✅ [TOOL] query_chunked_document completed`);
|
|
2441
|
-
return {
|
|
2442
|
-
success: true,
|
|
2443
|
-
data: {
|
|
2444
|
-
answer: result.answer,
|
|
2445
|
-
model: result.model,
|
|
2446
|
-
tokensUsed: result.tokensUsed,
|
|
2447
|
-
chunksQueried: args.file_names.length,
|
|
2448
|
-
filesUsed: result.filesUsed,
|
|
2449
|
-
},
|
|
2450
|
-
};
|
|
2451
|
-
}
|
|
2452
|
-
catch (error) {
|
|
2453
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2454
|
-
log.error(`❌ [TOOL] query_chunked_document failed: ${errorMessage}`);
|
|
2455
|
-
return { success: false, error: errorMessage };
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
// ==================== QUERY HISTORY ====================
|
|
2459
|
-
/**
|
|
2460
|
-
* Handle get_query_history tool
|
|
2461
|
-
*
|
|
2462
|
-
* Retrieves past NotebookLM queries for reviewing research sessions.
|
|
2463
|
-
*/
|
|
2464
|
-
async handleGetQueryHistory(args) {
|
|
2465
|
-
log.info(`🔧 [TOOL] get_query_history called`);
|
|
2466
|
-
try {
|
|
2467
|
-
const queryLogger = getQueryLogger();
|
|
2468
|
-
const limit = Math.min(args.limit ?? 50, 500); // Cap at 500
|
|
2469
|
-
let queries;
|
|
2470
|
-
if (args.search) {
|
|
2471
|
-
// Search across all queries
|
|
2472
|
-
queries = await queryLogger.searchQueries(args.search, { limit });
|
|
2473
|
-
log.info(` Searching for: "${args.search}"`);
|
|
2474
|
-
}
|
|
2475
|
-
else if (args.session_id) {
|
|
2476
|
-
// Filter by session
|
|
2477
|
-
queries = await queryLogger.getQueriesForSession(args.session_id);
|
|
2478
|
-
log.info(` Filtering by session: ${args.session_id}`);
|
|
2479
|
-
}
|
|
2480
|
-
else if (args.notebook_id) {
|
|
2481
|
-
// Filter by notebook
|
|
2482
|
-
queries = await queryLogger.getQueriesForNotebookId(args.notebook_id);
|
|
2483
|
-
log.info(` Filtering by notebook: ${args.notebook_id}`);
|
|
2484
|
-
}
|
|
2485
|
-
else if (args.date) {
|
|
2486
|
-
// Filter by date
|
|
2487
|
-
queries = await queryLogger.getQueriesForDate(args.date);
|
|
2488
|
-
log.info(` Filtering by date: ${args.date}`);
|
|
2489
|
-
}
|
|
2490
|
-
else {
|
|
2491
|
-
// Get recent queries
|
|
2492
|
-
queries = await queryLogger.getRecentQueries(limit);
|
|
2493
|
-
log.info(` Getting recent queries (limit: ${limit})`);
|
|
2494
|
-
}
|
|
2495
|
-
// Apply limit
|
|
2496
|
-
const limitedQueries = queries.slice(0, limit);
|
|
2497
|
-
log.success(`✅ [TOOL] get_query_history completed (${limitedQueries.length} queries)`);
|
|
2498
|
-
return {
|
|
2499
|
-
success: true,
|
|
2500
|
-
data: {
|
|
2501
|
-
count: limitedQueries.length,
|
|
2502
|
-
queries: limitedQueries,
|
|
2503
|
-
},
|
|
2504
|
-
};
|
|
2505
|
-
}
|
|
2506
|
-
catch (error) {
|
|
2507
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2508
|
-
log.error(`❌ [TOOL] get_query_history failed: ${errorMessage}`);
|
|
2509
|
-
return {
|
|
2510
|
-
success: false,
|
|
2511
|
-
error: errorMessage,
|
|
2512
|
-
};
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
// ==================== CHAT HISTORY ====================
|
|
2516
|
-
/**
|
|
2517
|
-
* Handle get_notebook_chat_history tool
|
|
2518
|
-
*
|
|
2519
|
-
* Extracts conversation history from a NotebookLM notebook's chat UI
|
|
2520
|
-
* using browser automation.
|
|
2521
|
-
*/
|
|
2522
|
-
async handleGetNotebookChatHistory(args) {
|
|
2523
|
-
log.info(`🔧 [TOOL] get_notebook_chat_history called${args.preview_only ? ' (preview mode)' : ''}`);
|
|
2524
|
-
try {
|
|
2525
|
-
// Resolve notebook URL
|
|
2526
|
-
let notebookUrl;
|
|
2527
|
-
let notebookName;
|
|
2528
|
-
if (args.notebook_url) {
|
|
2529
|
-
notebookUrl = validateNotebookUrl(args.notebook_url);
|
|
2530
|
-
}
|
|
2531
|
-
else if (args.notebook_id) {
|
|
2532
|
-
validateNotebookId(args.notebook_id);
|
|
2533
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
2534
|
-
if (!notebook) {
|
|
2535
|
-
return {
|
|
2536
|
-
success: false,
|
|
2537
|
-
error: `Notebook not found: ${args.notebook_id}. Use list_notebooks to see available notebooks.`,
|
|
2538
|
-
};
|
|
2539
|
-
}
|
|
2540
|
-
notebookUrl = notebook.url;
|
|
2541
|
-
notebookName = notebook.name;
|
|
2542
|
-
}
|
|
2543
|
-
else {
|
|
2544
|
-
// Try to use active notebook
|
|
2545
|
-
const activeNotebook = this.library.getActiveNotebook();
|
|
2546
|
-
if (!activeNotebook) {
|
|
2547
|
-
return {
|
|
2548
|
-
success: false,
|
|
2549
|
-
error: "No notebook specified. Provide notebook_id or notebook_url, or set an active notebook.",
|
|
2550
|
-
};
|
|
2551
|
-
}
|
|
2552
|
-
notebookUrl = activeNotebook.url;
|
|
2553
|
-
notebookName = activeNotebook.name;
|
|
2554
|
-
}
|
|
2555
|
-
log.info(` 📓 Extracting chat history from: ${notebookUrl}`);
|
|
2556
|
-
// Apply browser options if show_browser is set
|
|
2557
|
-
if (args.show_browser !== undefined) {
|
|
2558
|
-
applyBrowserOptions({ show: args.show_browser });
|
|
2559
|
-
}
|
|
2560
|
-
// Create a temporary session to navigate to the notebook
|
|
2561
|
-
const sessionId = `chat-history-${Date.now()}`;
|
|
2562
|
-
const session = await this.sessionManager.getOrCreateSession(sessionId, notebookUrl);
|
|
2563
|
-
try {
|
|
2564
|
-
// Get the page from the session
|
|
2565
|
-
const page = session.getPage();
|
|
2566
|
-
if (!page) {
|
|
2567
|
-
throw new Error("Failed to get page from session");
|
|
2568
|
-
}
|
|
2569
|
-
// Wait a bit for the chat history to fully load
|
|
2570
|
-
await page.waitForTimeout(2000);
|
|
2571
|
-
const messages = await page.evaluate(() => {
|
|
2572
|
-
const result = [];
|
|
2573
|
-
// Get all message containers (both user and assistant)
|
|
2574
|
-
// User messages: .from-user-container / Assistant messages: .to-user-container
|
|
2575
|
-
// @ts-expect-error - DOM types available in browser context
|
|
2576
|
-
const allContainers = document.querySelectorAll(".from-user-container, .to-user-container");
|
|
2577
|
-
let idx = 0;
|
|
2578
|
-
allContainers.forEach((container) => {
|
|
2579
|
-
const isUser = container.classList?.contains("from-user-container");
|
|
2580
|
-
const isAssistant = container.classList?.contains("to-user-container");
|
|
2581
|
-
if (isUser) {
|
|
2582
|
-
// User message - look for query text
|
|
2583
|
-
const queryText = container.querySelector(".query-text, .message-text-content, .user-message");
|
|
2584
|
-
if (queryText) {
|
|
2585
|
-
const content = queryText.innerText?.trim();
|
|
2586
|
-
if (content) {
|
|
2587
|
-
result.push({ role: "user", content, index: idx++ });
|
|
2588
|
-
}
|
|
2589
|
-
}
|
|
2590
|
-
else {
|
|
2591
|
-
// Fallback: get container text directly
|
|
2592
|
-
const content = container.innerText?.trim();
|
|
2593
|
-
if (content) {
|
|
2594
|
-
result.push({ role: "user", content, index: idx++ });
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
}
|
|
2598
|
-
else if (isAssistant) {
|
|
2599
|
-
// Assistant message
|
|
2600
|
-
const textContent = container.querySelector(".message-text-content");
|
|
2601
|
-
if (textContent) {
|
|
2602
|
-
const content = textContent.innerText?.trim();
|
|
2603
|
-
if (content) {
|
|
2604
|
-
result.push({ role: "assistant", content, index: idx++ });
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
});
|
|
2609
|
-
return result;
|
|
2610
|
-
});
|
|
2611
|
-
// Calculate stats
|
|
2612
|
-
const totalMessages = messages.length;
|
|
2613
|
-
const userMessages = messages.filter(m => m.role === "user").length;
|
|
2614
|
-
const assistantMessages = messages.filter(m => m.role === "assistant").length;
|
|
2615
|
-
// Preview mode - just return stats without content
|
|
2616
|
-
if (args.preview_only) {
|
|
2617
|
-
log.success(`✅ [TOOL] get_notebook_chat_history preview completed (${totalMessages} messages found)`);
|
|
2618
|
-
return {
|
|
2619
|
-
success: true,
|
|
2620
|
-
data: {
|
|
2621
|
-
notebook_url: notebookUrl,
|
|
2622
|
-
notebook_name: notebookName,
|
|
2623
|
-
total_messages: totalMessages,
|
|
2624
|
-
returned_messages: 0,
|
|
2625
|
-
user_messages: userMessages,
|
|
2626
|
-
assistant_messages: assistantMessages,
|
|
2627
|
-
},
|
|
2628
|
-
};
|
|
2629
|
-
}
|
|
2630
|
-
// Apply pagination (offset and limit)
|
|
2631
|
-
const offset = args.offset ?? 0;
|
|
2632
|
-
const limit = Math.min(args.limit ?? 50, 200);
|
|
2633
|
-
const startIdx = offset * 2; // offset is in pairs, convert to message count
|
|
2634
|
-
const endIdx = startIdx + (limit * 2);
|
|
2635
|
-
const paginatedMessages = messages.slice(startIdx, endIdx);
|
|
2636
|
-
const hasMore = endIdx < totalMessages;
|
|
2637
|
-
// Re-index the paginated messages
|
|
2638
|
-
const reindexedMessages = paginatedMessages.map((m, idx) => ({
|
|
2639
|
-
...m,
|
|
2640
|
-
index: startIdx + idx,
|
|
2641
|
-
}));
|
|
2642
|
-
// Export to file if requested
|
|
2643
|
-
if (args.output_file) {
|
|
2644
|
-
const fs = await import("fs/promises");
|
|
2645
|
-
const exportData = {
|
|
2646
|
-
notebook_url: notebookUrl,
|
|
2647
|
-
notebook_name: notebookName,
|
|
2648
|
-
exported_at: new Date().toISOString(),
|
|
2649
|
-
total_messages: totalMessages,
|
|
2650
|
-
user_messages: userMessages,
|
|
2651
|
-
assistant_messages: assistantMessages,
|
|
2652
|
-
messages: reindexedMessages,
|
|
2653
|
-
};
|
|
2654
|
-
await fs.writeFile(args.output_file, JSON.stringify(exportData, null, 2));
|
|
2655
|
-
log.success(`✅ [TOOL] get_notebook_chat_history exported to ${args.output_file}`);
|
|
2656
|
-
return {
|
|
2657
|
-
success: true,
|
|
2658
|
-
data: {
|
|
2659
|
-
notebook_url: notebookUrl,
|
|
2660
|
-
notebook_name: notebookName,
|
|
2661
|
-
total_messages: totalMessages,
|
|
2662
|
-
returned_messages: reindexedMessages.length,
|
|
2663
|
-
user_messages: userMessages,
|
|
2664
|
-
assistant_messages: assistantMessages,
|
|
2665
|
-
output_file: args.output_file,
|
|
2666
|
-
},
|
|
2667
|
-
};
|
|
2668
|
-
}
|
|
2669
|
-
log.success(`✅ [TOOL] get_notebook_chat_history completed (${reindexedMessages.length}/${totalMessages} messages)`);
|
|
2670
|
-
return {
|
|
2671
|
-
success: true,
|
|
2672
|
-
data: {
|
|
2673
|
-
notebook_url: notebookUrl,
|
|
2674
|
-
notebook_name: notebookName,
|
|
2675
|
-
total_messages: totalMessages,
|
|
2676
|
-
returned_messages: reindexedMessages.length,
|
|
2677
|
-
user_messages: userMessages,
|
|
2678
|
-
assistant_messages: assistantMessages,
|
|
2679
|
-
offset: offset,
|
|
2680
|
-
has_more: hasMore,
|
|
2681
|
-
messages: reindexedMessages,
|
|
2682
|
-
},
|
|
2683
|
-
};
|
|
2684
|
-
}
|
|
2685
|
-
finally {
|
|
2686
|
-
// Close the temporary session
|
|
2687
|
-
await this.sessionManager.closeSession(sessionId);
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
catch (error) {
|
|
2691
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2692
|
-
log.error(`❌ [TOOL] get_notebook_chat_history failed: ${errorMessage}`);
|
|
2693
|
-
return {
|
|
2694
|
-
success: false,
|
|
2695
|
-
error: errorMessage,
|
|
2696
|
-
};
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
// ==================== VIDEO OVERVIEW ====================
|
|
2700
|
-
/**
|
|
2701
|
-
* Handle generate_video_overview tool
|
|
2702
|
-
*
|
|
2703
|
-
* Generates a Video Overview via the Studio panel in NotebookLM.
|
|
2704
|
-
*/
|
|
2705
|
-
async handleGenerateVideoOverview(args) {
|
|
2706
|
-
log.info(`🔧 [TOOL] generate_video_overview called`);
|
|
2707
|
-
try {
|
|
2708
|
-
// Resolve notebook URL
|
|
2709
|
-
let notebookUrl = args.notebook_url;
|
|
2710
|
-
if (!notebookUrl && args.notebook_id) {
|
|
2711
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
2712
|
-
if (!notebook) {
|
|
2713
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
2714
|
-
}
|
|
2715
|
-
notebookUrl = notebook.url;
|
|
2716
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
2717
|
-
}
|
|
2718
|
-
else if (!notebookUrl) {
|
|
2719
|
-
const active = this.library.getActiveNotebook();
|
|
2720
|
-
if (active) {
|
|
2721
|
-
notebookUrl = active.url;
|
|
2722
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
2723
|
-
}
|
|
2724
|
-
else {
|
|
2725
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
// Validate URL
|
|
2729
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
2730
|
-
// Get the shared context manager from session manager
|
|
2731
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
2732
|
-
// Generate video
|
|
2733
|
-
const videoManager = new VideoManager(this.authManager, contextManager);
|
|
2734
|
-
const result = await videoManager.generateVideoOverview(safeUrl, args.style || "auto-select", args.format || "explainer");
|
|
2735
|
-
if (result.success) {
|
|
2736
|
-
log.success(`✅ [TOOL] generate_video_overview completed (status: ${result.status.status})`);
|
|
2737
|
-
}
|
|
2738
|
-
else {
|
|
2739
|
-
log.warning(`⚠️ [TOOL] generate_video_overview: ${result.error}`);
|
|
2740
|
-
}
|
|
2741
|
-
return {
|
|
2742
|
-
success: result.success,
|
|
2743
|
-
data: result,
|
|
2744
|
-
...(result.error && { error: result.error }),
|
|
2745
|
-
};
|
|
2746
|
-
}
|
|
2747
|
-
catch (error) {
|
|
2748
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2749
|
-
log.error(`❌ [TOOL] generate_video_overview failed: ${errorMessage}`);
|
|
2750
|
-
return {
|
|
2751
|
-
success: false,
|
|
2752
|
-
error: errorMessage,
|
|
2753
|
-
};
|
|
2754
|
-
}
|
|
2755
|
-
}
|
|
2756
|
-
/**
|
|
2757
|
-
* Handle get_video_status tool
|
|
2758
|
-
*
|
|
2759
|
-
* Checks the video generation status for a notebook.
|
|
2760
|
-
*/
|
|
2761
|
-
async handleGetVideoStatus(args) {
|
|
2762
|
-
log.info(`🔧 [TOOL] get_video_status called`);
|
|
2763
|
-
try {
|
|
2764
|
-
// Resolve notebook URL
|
|
2765
|
-
let notebookUrl = args.notebook_url;
|
|
2766
|
-
if (!notebookUrl && args.notebook_id) {
|
|
2767
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
2768
|
-
if (!notebook) {
|
|
2769
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
2770
|
-
}
|
|
2771
|
-
notebookUrl = notebook.url;
|
|
2772
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
2773
|
-
}
|
|
2774
|
-
else if (!notebookUrl) {
|
|
2775
|
-
const active = this.library.getActiveNotebook();
|
|
2776
|
-
if (active) {
|
|
2777
|
-
notebookUrl = active.url;
|
|
2778
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
2779
|
-
}
|
|
2780
|
-
else {
|
|
2781
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
// Validate URL
|
|
2785
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
2786
|
-
// Get the shared context manager from session manager
|
|
2787
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
2788
|
-
// Get status
|
|
2789
|
-
const videoManager = new VideoManager(this.authManager, contextManager);
|
|
2790
|
-
const status = await videoManager.getVideoStatus(safeUrl);
|
|
2791
|
-
log.success(`✅ [TOOL] get_video_status completed (status: ${status.status})`);
|
|
2792
|
-
return {
|
|
2793
|
-
success: true,
|
|
2794
|
-
data: status,
|
|
2795
|
-
};
|
|
2796
|
-
}
|
|
2797
|
-
catch (error) {
|
|
2798
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2799
|
-
log.error(`❌ [TOOL] get_video_status failed: ${errorMessage}`);
|
|
2800
|
-
return {
|
|
2801
|
-
success: false,
|
|
2802
|
-
error: errorMessage,
|
|
2803
|
-
};
|
|
2804
|
-
}
|
|
2805
|
-
}
|
|
2806
|
-
// ==================== DATA TABLES ====================
|
|
2807
|
-
/**
|
|
2808
|
-
* Handle generate_data_table tool
|
|
2809
|
-
*
|
|
2810
|
-
* Generates a structured Data Table via the Studio panel in NotebookLM.
|
|
2811
|
-
*/
|
|
2812
|
-
async handleGenerateDataTable(args) {
|
|
2813
|
-
log.info(`🔧 [TOOL] generate_data_table called`);
|
|
2814
|
-
try {
|
|
2815
|
-
// Resolve notebook URL
|
|
2816
|
-
let notebookUrl = args.notebook_url;
|
|
2817
|
-
if (!notebookUrl && args.notebook_id) {
|
|
2818
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
2819
|
-
if (!notebook) {
|
|
2820
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
2821
|
-
}
|
|
2822
|
-
notebookUrl = notebook.url;
|
|
2823
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
2824
|
-
}
|
|
2825
|
-
else if (!notebookUrl) {
|
|
2826
|
-
const active = this.library.getActiveNotebook();
|
|
2827
|
-
if (active) {
|
|
2828
|
-
notebookUrl = active.url;
|
|
2829
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
2830
|
-
}
|
|
2831
|
-
else {
|
|
2832
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
2835
|
-
// Validate URL
|
|
2836
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
2837
|
-
// Get the shared context manager from session manager
|
|
2838
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
2839
|
-
// Generate data table
|
|
2840
|
-
const dataTableManager = new DataTableManager(this.authManager, contextManager);
|
|
2841
|
-
const result = await dataTableManager.generateDataTable(safeUrl);
|
|
2842
|
-
if (result.success) {
|
|
2843
|
-
log.success(`✅ [TOOL] generate_data_table completed (status: ${result.status.status})`);
|
|
2844
|
-
}
|
|
2845
|
-
else {
|
|
2846
|
-
log.warning(`⚠️ [TOOL] generate_data_table: ${result.error}`);
|
|
2847
|
-
}
|
|
2848
|
-
return {
|
|
2849
|
-
success: result.success,
|
|
2850
|
-
data: result,
|
|
2851
|
-
...(result.error && { error: result.error }),
|
|
2852
|
-
};
|
|
2853
|
-
}
|
|
2854
|
-
catch (error) {
|
|
2855
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2856
|
-
log.error(`❌ [TOOL] generate_data_table failed: ${errorMessage}`);
|
|
2857
|
-
return {
|
|
2858
|
-
success: false,
|
|
2859
|
-
error: errorMessage,
|
|
2860
|
-
};
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
/**
|
|
2864
|
-
* Handle get_data_table tool
|
|
2865
|
-
*
|
|
2866
|
-
* Extracts the generated Data Table content from a notebook.
|
|
2867
|
-
*/
|
|
2868
|
-
async handleGetDataTable(args) {
|
|
2869
|
-
log.info(`🔧 [TOOL] get_data_table called`);
|
|
2870
|
-
try {
|
|
2871
|
-
// Resolve notebook URL
|
|
2872
|
-
let notebookUrl = args.notebook_url;
|
|
2873
|
-
if (!notebookUrl && args.notebook_id) {
|
|
2874
|
-
const notebook = this.library.getNotebook(args.notebook_id);
|
|
2875
|
-
if (!notebook) {
|
|
2876
|
-
throw new Error(`Notebook not found in library: ${args.notebook_id}`);
|
|
2877
|
-
}
|
|
2878
|
-
notebookUrl = notebook.url;
|
|
2879
|
-
log.info(` Resolved notebook: ${notebook.name}`);
|
|
2880
|
-
}
|
|
2881
|
-
else if (!notebookUrl) {
|
|
2882
|
-
const active = this.library.getActiveNotebook();
|
|
2883
|
-
if (active) {
|
|
2884
|
-
notebookUrl = active.url;
|
|
2885
|
-
log.info(` Using active notebook: ${active.name}`);
|
|
2886
|
-
}
|
|
2887
|
-
else {
|
|
2888
|
-
throw new Error("No notebook specified. Provide notebook_id or notebook_url.");
|
|
2889
|
-
}
|
|
2890
|
-
}
|
|
2891
|
-
// Validate URL
|
|
2892
|
-
const safeUrl = validateNotebookUrl(notebookUrl);
|
|
2893
|
-
// Get the shared context manager from session manager
|
|
2894
|
-
const contextManager = this.sessionManager.getContextManager();
|
|
2895
|
-
// Get data table
|
|
2896
|
-
const dataTableManager = new DataTableManager(this.authManager, contextManager);
|
|
2897
|
-
const result = await dataTableManager.getDataTable(safeUrl);
|
|
2898
|
-
if (result.success) {
|
|
2899
|
-
log.success(`✅ [TOOL] get_data_table completed (${result.table?.totalRows} rows x ${result.table?.totalColumns} cols)`);
|
|
2900
|
-
}
|
|
2901
|
-
else {
|
|
2902
|
-
log.warning(`⚠️ [TOOL] get_data_table: ${result.error}`);
|
|
2903
|
-
}
|
|
2904
|
-
return {
|
|
2905
|
-
success: result.success,
|
|
2906
|
-
data: result,
|
|
2907
|
-
...(result.error && { error: result.error }),
|
|
2908
|
-
};
|
|
2909
|
-
}
|
|
2910
|
-
catch (error) {
|
|
2911
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2912
|
-
log.error(`❌ [TOOL] get_data_table failed: ${errorMessage}`);
|
|
2913
|
-
return {
|
|
2914
|
-
success: false,
|
|
2915
|
-
error: errorMessage,
|
|
2916
|
-
};
|
|
2917
|
-
}
|
|
2918
|
-
}
|
|
2919
|
-
// ==================== CLEANUP ====================
|
|
2920
|
-
/**
|
|
2921
|
-
* Cleanup all resources (called on server shutdown)
|
|
2922
|
-
*/
|
|
2923
|
-
async cleanup() {
|
|
2924
|
-
log.info(`🧹 Cleaning up tool handlers...`);
|
|
2925
|
-
await this.sessionManager.closeAllSessions();
|
|
2926
|
-
log.success(`✅ Tool handlers cleanup complete`);
|
|
2927
|
-
}
|
|
2928
|
-
}
|
|
2929
|
-
//# sourceMappingURL=handlers.js.map
|