diff --git a/CHANGELOG.md b/CHANGELOG.md index 69d87fb..5be79c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.3.12] - 2026-01-12 + +### Added +- New GM-only setting to allow private journal context in Archivist Chat; GM requests now send `gm_permissions` to `/v1/ask` when enabled. + ## [1.3.11] - 2025-01-15 ### Fixed diff --git a/README.md b/README.md index 3af49e7..423ce09 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ All sheets include: Available in Game Settings → Module Settings → Archivist Sync: - **API Key** (world): Your Archivist API key (obfuscated in the UI for security) +- **Allow GM private journal context in chat** (world): When enabled, GM chat requests include private journal context during retrieval; non-GM requests never include it. - **Run World Setup Again** (menu): Reset initialization and relaunch the setup wizard - **Projection: Sidecar Only** (world): When enabled, never modify core system data fields; store descriptions only in sidecar journals. @@ -153,7 +154,8 @@ Examples (representative; bodies vary by type): "messages": [ { "role": "user", "content": "Who is the duke?" } ], - "stream": true + "stream": true, + "gm_permissions": false } ``` @@ -167,6 +169,7 @@ Examples (representative; bodies vary by type): - The API key is stored as a world-scoped setting and obfuscated in the UI. - Real‑Time Sync and the "Sync with Archivist" button execute only on GM clients. - The sidebar chat is available to all users once the world is configured and initialized. +- GM chat requests can include private journal context during retrieval when the GM setting is enabled; non-GM requests always send `gm_permissions: false`. - All requests use HTTPS endpoints with CORS-safe headers and exponential backoff on `429` and network failures. ## Troubleshooting @@ -269,4 +272,4 @@ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelin --- -Remember to update `module.json` URLs (`url`, `manifest`, `download`, `bugs`) to point to your repository and releases. \ No newline at end of file +Remember to update `module.json` URLs (`url`, `manifest`, `download`, `bugs`) to point to your repository and releases. diff --git a/lang/en.json b/lang/en.json index 2c849e5..b742d46 100644 --- a/lang/en.json +++ b/lang/en.json @@ -34,6 +34,10 @@ "None": "None" } }, + "ChatGmPermissions": { + "Name": "Allow GM private journal context in chat", + "Hint": "When enabled, GM chat requests include private journal context during retrieval. Non-GM users never send private context." + }, "MappingOverride": { "Name": "Mapping Override (JSON)", "Hint": "Optional JSON rules to customize importer field mapping" @@ -317,4 +321,4 @@ } } } -} \ No newline at end of file +} diff --git a/module.json b/module.json index cba0cb1..dc7e52e 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "email": "cameron.b.llewellyn@gmail.com" } ], - "version": "1.3.11", + "version": "1.3.12", "compatibility": { "minimum": "13.341", "verified": "13.346" diff --git a/package.json b/package.json index 21fa292..8fdbd4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "archivist-sync", - "version": "1.3.11", + "version": "1.3.12", "description": "A simple Foundry VTT module for fetching world data from an API endpoint using an API key.", "type": "module", "scripts": { @@ -29,4 +29,4 @@ "eslint-plugin-prettier": "^5.1.3", "prettier": "^3.2.5" } -} \ No newline at end of file +} diff --git a/scripts/dialogs/ask-chat-window.js b/scripts/dialogs/ask-chat-window.js index 46c54fe..22f2466 100644 --- a/scripts/dialogs/ask-chat-window.js +++ b/scripts/dialogs/ask-chat-window.js @@ -292,6 +292,8 @@ export class AskChatWindow { const apiKey = settingsManager.getApiKey(); const worldId = settingsManager.getSelectedWorldId(); + const gmPermissions = + !!game.user?.isGM && !!settingsManager.getChatGmPermissionsEnabled?.(); this._isStreaming = true; this.render(false); const controller = new AbortController(); @@ -301,6 +303,7 @@ export class AskChatWindow { apiKey, worldId, recent, + gmPermissions, (chunk) => { if (assistantMsg.typing) assistantMsg.typing = false; assistantMsg.content += chunk; diff --git a/scripts/modules/config.js b/scripts/modules/config.js index fb2368d..d2ec55e 100644 --- a/scripts/modules/config.js +++ b/scripts/modules/config.js @@ -118,6 +118,16 @@ export const SETTINGS = { default: 'all', // 'all' | 'gm' | 'none' }, + CHAT_GM_PERMISSIONS: { + key: 'chatGmPermissions', + name: 'ARCHIVIST_SYNC.Settings.ChatGmPermissions.Name', + hint: 'ARCHIVIST_SYNC.Settings.ChatGmPermissions.Hint', + scope: 'world', + config: true, + type: Boolean, + default: true, + }, + PROJECT_DESCRIPTIONS: { key: 'projectDescriptions', name: 'ARCHIVIST_SYNC.Settings.ProjectDescriptions.Name', diff --git a/scripts/modules/settings-manager.js b/scripts/modules/settings-manager.js index d4f25a7..52ab32c 100644 --- a/scripts/modules/settings-manager.js +++ b/scripts/modules/settings-manager.js @@ -27,6 +27,7 @@ export class SettingsManager { this._registerRealtimeSync(); this._registerChatHistory(); this._registerChatVisibility(); + this._registerChatGmPermissions(); this._registerUpdateApiKeyMenu(); this._registerRunSetupAgainMenu(); this._registerDocumentationMenu(); @@ -534,6 +535,18 @@ export class SettingsManager { return hasValidWorldSelection && isInitialized; } + /** + * Check if GM-only private journal context is enabled for chat + * @returns {boolean} + */ + getChatGmPermissionsEnabled() { + try { + return !!this.getSetting(SETTINGS.CHAT_GM_PERMISSIONS.key); + } catch (_) { + return true; + } + } + /** * Check if world is initialized with Archivist * @returns {boolean} True if world has been initialized @@ -683,6 +696,18 @@ export class SettingsManager { }); } + _registerChatGmPermissions() { + const setting = SETTINGS.CHAT_GM_PERMISSIONS; + game.settings.register(this.moduleId, setting.key, { + name: game.i18n.localize(setting.name), + hint: game.i18n.localize(setting.hint), + scope: setting.scope, + config: setting.config, + type: setting.type, + default: setting.default, + }); + } + /** * Handle changes that affect chat availability * @private diff --git a/scripts/services/archivist-api.js b/scripts/services/archivist-api.js index dd0b78b..38bb05a 100644 --- a/scripts/services/archivist-api.js +++ b/scripts/services/archivist-api.js @@ -1217,9 +1217,10 @@ export class ArchivistApiService { * @param {string} apiKey * @param {string} campaignId * @param {Array<{role:'user'|'assistant',content:string}>} messages + * @param {boolean} gmPermissions * @returns {Promise<{success:boolean, answer?:string, monthlyTokensRemaining?:number, hourlyTokensRemaining?:number, message?:string}>} */ - async ask(apiKey, campaignId, messages) { + async ask(apiKey, campaignId, messages, gmPermissions = false) { try { const url = `${this._rootBase()}/v1/ask`; const headers = { @@ -1228,7 +1229,11 @@ export class ArchivistApiService { const r = await fetch(url, { method: 'POST', headers, - body: JSON.stringify({ campaign_id: campaignId, messages }), + body: JSON.stringify({ + campaign_id: campaignId, + messages, + gm_permissions: !!gmPermissions, + }), }); const data = await this._handleResponse(r); return { @@ -1248,11 +1253,26 @@ export class ArchivistApiService { * @param {string} apiKey * @param {string} campaignId * @param {Array<{role:'user'|'assistant',content:string}>} messages + * @param {boolean} gmPermissions * @param {(chunk:string)=>void} onChunk * @param {(final:{text:string, monthlyTokensRemaining?:number, hourlyTokensRemaining?:number})=>void} onDone * @param {AbortSignal} [signal] */ - async askStream(apiKey, campaignId, messages, onChunk, onDone, signal) { + async askStream( + apiKey, + campaignId, + messages, + gmPermissions, + onChunk, + onDone, + signal + ) { + if (typeof gmPermissions === 'function') { + signal = onDone; + onDone = onChunk; + onChunk = gmPermissions; + gmPermissions = false; + } const url = `${this._rootBase()}/v1/ask`; const headers = { ...this._createHeaders(apiKey, { method: 'POST', body: '1' }), @@ -1263,6 +1283,7 @@ export class ArchivistApiService { campaign_id: campaignId, messages, stream: true, + gm_permissions: !!gmPermissions, }); const resp = await fetch(url, { method: 'POST', headers, body, signal }); if (!resp.ok) {