Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
}
```

Expand All @@ -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
Expand Down Expand Up @@ -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.
Remember to update `module.json` URLs (`url`, `manifest`, `download`, `bugs`) to point to your repository and releases.
6 changes: 5 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -317,4 +321,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion module.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -29,4 +29,4 @@
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5"
}
}
}
3 changes: 3 additions & 0 deletions scripts/dialogs/ask-chat-window.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -301,6 +303,7 @@ export class AskChatWindow {
apiKey,
worldId,
recent,
gmPermissions,
(chunk) => {
if (assistantMsg.typing) assistantMsg.typing = false;
assistantMsg.content += chunk;
Expand Down
10 changes: 10 additions & 0 deletions scripts/modules/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
25 changes: 25 additions & 0 deletions scripts/modules/settings-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class SettingsManager {
this._registerRealtimeSync();
this._registerChatHistory();
this._registerChatVisibility();
this._registerChatGmPermissions();
this._registerUpdateApiKeyMenu();
this._registerRunSetupAgainMenu();
this._registerDocumentationMenu();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
27 changes: 24 additions & 3 deletions scripts/services/archivist-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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 {
Expand All @@ -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' }),
Expand All @@ -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) {
Expand Down