run-dmcp 0.1.0

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.
Files changed (167) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +79 -0
  3. package/dist/__tests__/engineVocabulary.test.d.ts +1 -0
  4. package/dist/__tests__/engineVocabulary.test.js +147 -0
  5. package/dist/db/__tests__/connection.test.d.ts +1 -0
  6. package/dist/db/__tests__/connection.test.js +72 -0
  7. package/dist/db/__tests__/testDb.d.ts +33 -0
  8. package/dist/db/__tests__/testDb.js +41 -0
  9. package/dist/db/connection.d.ts +22 -0
  10. package/dist/db/connection.js +107 -0
  11. package/dist/db/schema.d.ts +1 -0
  12. package/dist/db/schema.js +725 -0
  13. package/dist/events/emitter.d.ts +22 -0
  14. package/dist/events/emitter.js +71 -0
  15. package/dist/http/server.d.ts +3 -0
  16. package/dist/http/server.js +649 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +92 -0
  19. package/dist/register/abilities.d.ts +2 -0
  20. package/dist/register/abilities.js +165 -0
  21. package/dist/register/audio.d.ts +2 -0
  22. package/dist/register/audio.js +326 -0
  23. package/dist/register/batch.d.ts +2 -0
  24. package/dist/register/batch.js +343 -0
  25. package/dist/register/character.d.ts +2 -0
  26. package/dist/register/character.js +324 -0
  27. package/dist/register/combat.d.ts +2 -0
  28. package/dist/register/combat.js +207 -0
  29. package/dist/register/core.d.ts +2 -0
  30. package/dist/register/core.js +1040 -0
  31. package/dist/register/display.d.ts +2 -0
  32. package/dist/register/display.js +263 -0
  33. package/dist/register/factions.d.ts +2 -0
  34. package/dist/register/factions.js +186 -0
  35. package/dist/register/images.d.ts +2 -0
  36. package/dist/register/images.js +400 -0
  37. package/dist/register/inventory.d.ts +2 -0
  38. package/dist/register/inventory.js +115 -0
  39. package/dist/register/mcp-prompts.d.ts +2 -0
  40. package/dist/register/mcp-prompts.js +684 -0
  41. package/dist/register/mcp-resources.d.ts +2 -0
  42. package/dist/register/mcp-resources.js +335 -0
  43. package/dist/register/narrative.d.ts +2 -0
  44. package/dist/register/narrative.js +242 -0
  45. package/dist/register/notes.d.ts +2 -0
  46. package/dist/register/notes.js +170 -0
  47. package/dist/register/pause.d.ts +2 -0
  48. package/dist/register/pause.js +580 -0
  49. package/dist/register/quests.d.ts +2 -0
  50. package/dist/register/quests.js +118 -0
  51. package/dist/register/relationships.d.ts +2 -0
  52. package/dist/register/relationships.js +147 -0
  53. package/dist/register/resources.d.ts +2 -0
  54. package/dist/register/resources.js +277 -0
  55. package/dist/register/secrets.d.ts +2 -0
  56. package/dist/register/secrets.js +192 -0
  57. package/dist/register/status.d.ts +2 -0
  58. package/dist/register/status.js +130 -0
  59. package/dist/register/tables.d.ts +2 -0
  60. package/dist/register/tables.js +146 -0
  61. package/dist/register/tags.d.ts +2 -0
  62. package/dist/register/tags.js +114 -0
  63. package/dist/register/time.d.ts +2 -0
  64. package/dist/register/time.js +281 -0
  65. package/dist/register/world.d.ts +2 -0
  66. package/dist/register/world.js +127 -0
  67. package/dist/schemas/index.d.ts +921 -0
  68. package/dist/schemas/index.js +121 -0
  69. package/dist/test-setup.d.ts +1 -0
  70. package/dist/test-setup.js +13 -0
  71. package/dist/tools/__tests__/audio.test.d.ts +1 -0
  72. package/dist/tools/__tests__/audio.test.js +59 -0
  73. package/dist/tools/__tests__/conserved.test.d.ts +1 -0
  74. package/dist/tools/__tests__/conserved.test.js +488 -0
  75. package/dist/tools/__tests__/constraint.test.d.ts +1 -0
  76. package/dist/tools/__tests__/constraint.test.js +212 -0
  77. package/dist/tools/__tests__/expiry-consequences.test.d.ts +1 -0
  78. package/dist/tools/__tests__/expiry-consequences.test.js +110 -0
  79. package/dist/tools/__tests__/images.test.d.ts +1 -0
  80. package/dist/tools/__tests__/images.test.js +59 -0
  81. package/dist/tools/__tests__/relationship.test.d.ts +1 -0
  82. package/dist/tools/__tests__/relationship.test.js +132 -0
  83. package/dist/tools/__tests__/resource-constraints.test.d.ts +1 -0
  84. package/dist/tools/__tests__/resource-constraints.test.js +131 -0
  85. package/dist/tools/__tests__/resource.test.d.ts +1 -0
  86. package/dist/tools/__tests__/resource.test.js +190 -0
  87. package/dist/tools/__tests__/time.test.d.ts +1 -0
  88. package/dist/tools/__tests__/time.test.js +404 -0
  89. package/dist/tools/__tests__/timers.test.d.ts +1 -0
  90. package/dist/tools/__tests__/timers.test.js +426 -0
  91. package/dist/tools/__tests__/world.test.d.ts +1 -0
  92. package/dist/tools/__tests__/world.test.js +70 -0
  93. package/dist/tools/ability.d.ts +48 -0
  94. package/dist/tools/ability.js +238 -0
  95. package/dist/tools/audio.d.ts +24 -0
  96. package/dist/tools/audio.js +365 -0
  97. package/dist/tools/character.d.ts +70 -0
  98. package/dist/tools/character.js +309 -0
  99. package/dist/tools/combat.d.ts +13 -0
  100. package/dist/tools/combat.js +195 -0
  101. package/dist/tools/constraint.d.ts +132 -0
  102. package/dist/tools/constraint.js +269 -0
  103. package/dist/tools/dice.d.ts +23 -0
  104. package/dist/tools/dice.js +111 -0
  105. package/dist/tools/display.d.ts +120 -0
  106. package/dist/tools/display.js +528 -0
  107. package/dist/tools/faction.d.ts +61 -0
  108. package/dist/tools/faction.js +269 -0
  109. package/dist/tools/game.d.ts +96 -0
  110. package/dist/tools/game.js +526 -0
  111. package/dist/tools/image-prompt.d.ts +49 -0
  112. package/dist/tools/image-prompt.js +479 -0
  113. package/dist/tools/images.d.ts +47 -0
  114. package/dist/tools/images.js +449 -0
  115. package/dist/tools/inventory.d.ts +20 -0
  116. package/dist/tools/inventory.js +145 -0
  117. package/dist/tools/narrative.d.ts +58 -0
  118. package/dist/tools/narrative.js +237 -0
  119. package/dist/tools/notes.d.ts +41 -0
  120. package/dist/tools/notes.js +220 -0
  121. package/dist/tools/pause.d.ts +110 -0
  122. package/dist/tools/pause.js +1254 -0
  123. package/dist/tools/quest.d.ts +34 -0
  124. package/dist/tools/quest.js +164 -0
  125. package/dist/tools/relationship.d.ts +74 -0
  126. package/dist/tools/relationship.js +324 -0
  127. package/dist/tools/resource.d.ts +93 -0
  128. package/dist/tools/resource.js +374 -0
  129. package/dist/tools/rules.d.ts +4 -0
  130. package/dist/tools/rules.js +30 -0
  131. package/dist/tools/secrets.d.ts +49 -0
  132. package/dist/tools/secrets.js +195 -0
  133. package/dist/tools/status.d.ts +36 -0
  134. package/dist/tools/status.js +218 -0
  135. package/dist/tools/tables.d.ts +33 -0
  136. package/dist/tools/tables.js +209 -0
  137. package/dist/tools/tags.d.ts +52 -0
  138. package/dist/tools/tags.js +176 -0
  139. package/dist/tools/time.d.ts +33 -0
  140. package/dist/tools/time.js +276 -0
  141. package/dist/tools/timers.d.ts +41 -0
  142. package/dist/tools/timers.js +215 -0
  143. package/dist/tools/world.d.ts +78 -0
  144. package/dist/tools/world.js +331 -0
  145. package/dist/types/index.d.ts +969 -0
  146. package/dist/types/index.js +1 -0
  147. package/dist/utils/__tests__/json.test.d.ts +1 -0
  148. package/dist/utils/__tests__/json.test.js +55 -0
  149. package/dist/utils/__tests__/validation.test.d.ts +1 -0
  150. package/dist/utils/__tests__/validation.test.js +90 -0
  151. package/dist/utils/errors.d.ts +44 -0
  152. package/dist/utils/errors.js +121 -0
  153. package/dist/utils/json.d.ts +9 -0
  154. package/dist/utils/json.js +23 -0
  155. package/dist/utils/logger.d.ts +7 -0
  156. package/dist/utils/logger.js +50 -0
  157. package/dist/utils/output-schemas.d.ts +594 -0
  158. package/dist/utils/output-schemas.js +331 -0
  159. package/dist/utils/tool-annotations.d.ts +147 -0
  160. package/dist/utils/tool-annotations.js +98 -0
  161. package/dist/utils/validation.d.ts +34 -0
  162. package/dist/utils/validation.js +52 -0
  163. package/dist/utils/verbosity.d.ts +57 -0
  164. package/dist/utils/verbosity.js +67 -0
  165. package/dist/utils/webui.d.ts +20 -0
  166. package/dist/utils/webui.js +35 -0
  167. package/package.json +75 -0
@@ -0,0 +1,237 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { getDatabase } from "../db/connection.js";
3
+ import { safeJsonParse } from "../utils/json.js";
4
+ import { gameEvents } from "../events/emitter.js";
5
+ import { validateGameExists } from "./game.js";
6
+ export function logEvent(params) {
7
+ // Validate game exists to prevent orphaned records
8
+ validateGameExists(params.gameId);
9
+ const db = getDatabase();
10
+ const id = uuidv4();
11
+ const timestamp = new Date().toISOString();
12
+ const stmt = db.prepare(`
13
+ INSERT INTO narrative_events (id, game_id, event_type, content, metadata, timestamp)
14
+ VALUES (?, ?, ?, ?, ?, ?)
15
+ `);
16
+ stmt.run(id, params.gameId, params.eventType, params.content, JSON.stringify(params.metadata || {}), timestamp);
17
+ const event = {
18
+ id,
19
+ gameId: params.gameId,
20
+ eventType: params.eventType,
21
+ content: params.content,
22
+ metadata: params.metadata || {},
23
+ timestamp,
24
+ };
25
+ // Emit realtime event
26
+ gameEvents.emit({
27
+ type: "narrative:created",
28
+ gameId: params.gameId,
29
+ entityId: id,
30
+ entityType: "narrative",
31
+ timestamp,
32
+ data: event,
33
+ });
34
+ return event;
35
+ }
36
+ export function getEvent(id) {
37
+ const db = getDatabase();
38
+ const stmt = db.prepare(`SELECT * FROM narrative_events WHERE id = ?`);
39
+ const row = stmt.get(id);
40
+ if (!row)
41
+ return null;
42
+ return {
43
+ id: row.id,
44
+ gameId: row.game_id,
45
+ eventType: row.event_type,
46
+ content: row.content,
47
+ metadata: safeJsonParse(row.metadata, {}),
48
+ timestamp: row.timestamp,
49
+ };
50
+ }
51
+ export function getHistory(gameId, options) {
52
+ const db = getDatabase();
53
+ let query = `SELECT * FROM narrative_events WHERE game_id = ?`;
54
+ const params = [gameId];
55
+ if (options?.eventType) {
56
+ query += ` AND event_type = ?`;
57
+ params.push(options.eventType);
58
+ }
59
+ if (options?.since) {
60
+ query += ` AND timestamp >= ?`;
61
+ params.push(options.since);
62
+ }
63
+ query += ` ORDER BY timestamp DESC`;
64
+ if (options?.limit) {
65
+ query += ` LIMIT ?`;
66
+ params.push(options.limit);
67
+ }
68
+ if (options?.offset) {
69
+ query += ` OFFSET ?`;
70
+ params.push(options.offset);
71
+ }
72
+ const stmt = db.prepare(query);
73
+ const rows = stmt.all(...params);
74
+ // Return in chronological order
75
+ return rows
76
+ .map((row) => ({
77
+ id: row.id,
78
+ gameId: row.game_id,
79
+ eventType: row.event_type,
80
+ content: row.content,
81
+ metadata: safeJsonParse(row.metadata, {}),
82
+ timestamp: row.timestamp,
83
+ }))
84
+ .reverse();
85
+ }
86
+ export function getRecentHistory(gameId, count = 10) {
87
+ return getHistory(gameId, { limit: count });
88
+ }
89
+ export function getSummary(gameId) {
90
+ const db = getDatabase();
91
+ const countResult = db
92
+ .prepare(`SELECT COUNT(*) as count FROM narrative_events WHERE game_id = ?`)
93
+ .get(gameId);
94
+ const typeResults = db
95
+ .prepare(`SELECT event_type, COUNT(*) as count FROM narrative_events WHERE game_id = ? GROUP BY event_type`)
96
+ .all(gameId);
97
+ const eventTypes = {};
98
+ for (const row of typeResults) {
99
+ eventTypes[row.event_type] = row.count;
100
+ }
101
+ const firstResult = db
102
+ .prepare(`SELECT timestamp FROM narrative_events WHERE game_id = ? ORDER BY timestamp ASC LIMIT 1`)
103
+ .get(gameId);
104
+ const lastResult = db
105
+ .prepare(`SELECT timestamp FROM narrative_events WHERE game_id = ? ORDER BY timestamp DESC LIMIT 1`)
106
+ .get(gameId);
107
+ const recentEvents = getRecentHistory(gameId, 5);
108
+ return {
109
+ totalEvents: countResult.count,
110
+ eventTypes,
111
+ firstEvent: firstResult?.timestamp || null,
112
+ lastEvent: lastResult?.timestamp || null,
113
+ recentEvents,
114
+ };
115
+ }
116
+ export function deleteEvent(id) {
117
+ const db = getDatabase();
118
+ const stmt = db.prepare(`DELETE FROM narrative_events WHERE id = ?`);
119
+ const result = stmt.run(id);
120
+ return result.changes > 0;
121
+ }
122
+ export function clearHistory(gameId) {
123
+ const db = getDatabase();
124
+ const stmt = db.prepare(`DELETE FROM narrative_events WHERE game_id = ?`);
125
+ const result = stmt.run(gameId);
126
+ return result.changes;
127
+ }
128
+ export function exportStoryData(gameId, style) {
129
+ const db = getDatabase();
130
+ // Get game info
131
+ const game = db
132
+ .prepare(`SELECT name, setting, style, created_at FROM games WHERE id = ?`)
133
+ .get(gameId);
134
+ if (!game)
135
+ return null;
136
+ // Get all characters
137
+ const characters = db
138
+ .prepare(`SELECT name, is_player, notes FROM characters WHERE game_id = ?`)
139
+ .all(gameId);
140
+ // Get all locations
141
+ const locations = db
142
+ .prepare(`SELECT name, description FROM locations WHERE game_id = ?`)
143
+ .all(gameId);
144
+ // Get all quests
145
+ const quests = db
146
+ .prepare(`SELECT name, description, status, objectives FROM quests WHERE game_id = ?`)
147
+ .all(gameId);
148
+ // Get all narrative events in chronological order
149
+ const events = db
150
+ .prepare(`SELECT * FROM narrative_events WHERE game_id = ? ORDER BY timestamp ASC`)
151
+ .all(gameId);
152
+ const narrativeEvents = events.map((row) => ({
153
+ id: row.id,
154
+ gameId: row.game_id,
155
+ eventType: row.event_type,
156
+ content: row.content,
157
+ metadata: safeJsonParse(row.metadata, {}),
158
+ timestamp: row.timestamp,
159
+ }));
160
+ // Group events into chapters (by day or significant breaks)
161
+ const chapters = groupEventsIntoChapters(narrativeEvents);
162
+ return {
163
+ game: {
164
+ name: game.name,
165
+ setting: game.setting,
166
+ style: game.style,
167
+ createdAt: game.created_at,
168
+ },
169
+ characters: characters.map((c) => ({
170
+ name: c.name,
171
+ isPlayer: c.is_player === 1,
172
+ notes: c.notes,
173
+ })),
174
+ locations: locations.map((l) => ({
175
+ name: l.name,
176
+ description: l.description,
177
+ })),
178
+ quests: quests.map((q) => ({
179
+ name: q.name,
180
+ description: q.description,
181
+ status: q.status,
182
+ objectives: safeJsonParse(q.objectives, []),
183
+ })),
184
+ chapters,
185
+ totalEvents: narrativeEvents.length,
186
+ exportStyle: style,
187
+ instruction: getStyleInstruction(style),
188
+ };
189
+ }
190
+ function groupEventsIntoChapters(events) {
191
+ if (events.length === 0)
192
+ return [];
193
+ const chapters = [];
194
+ let currentChapter = [];
195
+ let currentDate = null;
196
+ let chapterNumber = 1;
197
+ for (const event of events) {
198
+ const eventDate = event.timestamp.split("T")[0];
199
+ // Start new chapter on day change or after major events
200
+ const isMajorEvent = ["quest_completed", "combat_resolved", "location_change"].includes(event.eventType);
201
+ if (currentDate && (eventDate !== currentDate || (isMajorEvent && currentChapter.length > 5))) {
202
+ if (currentChapter.length > 0) {
203
+ chapters.push({
204
+ title: `Chapter ${chapterNumber}`,
205
+ events: currentChapter,
206
+ });
207
+ chapterNumber++;
208
+ currentChapter = [];
209
+ }
210
+ }
211
+ currentChapter.push(event);
212
+ currentDate = eventDate;
213
+ }
214
+ // Add final chapter
215
+ if (currentChapter.length > 0) {
216
+ chapters.push({
217
+ title: `Chapter ${chapterNumber}`,
218
+ events: currentChapter,
219
+ });
220
+ }
221
+ return chapters;
222
+ }
223
+ function getStyleInstruction(style) {
224
+ const styles = {
225
+ "literary-fiction": "Write in a sophisticated, literary style with rich prose, deep character introspection, and thematic depth. Focus on the human condition and emotional truth.",
226
+ "pulp-adventure": "Write in an exciting, fast-paced pulp style with vivid action, bold heroes, and thrilling cliffhangers. Keep it punchy and entertaining.",
227
+ "epic-fantasy": "Write in a grand, sweeping epic fantasy style with rich world-building, dramatic moments, and mythic undertones. Channel Tolkien and Jordan.",
228
+ "noir": "Write in a hardboiled noir style with terse prose, moral ambiguity, and atmospheric descriptions. First person works well here.",
229
+ "horror": "Write in an atmospheric horror style building dread and tension. Focus on the unsettling and the unknown. Use restraint for maximum effect.",
230
+ "comedic": "Write in a witty, humorous style finding the absurdity and comedy in the events. Include clever dialogue and amusing observations.",
231
+ "young-adult": "Write in an accessible, engaging young adult style with relatable characters and clear prose. Balance action with emotional moments.",
232
+ "screenplay": "Format as a screenplay with scene headings, action lines, and dialogue. Focus on visual storytelling and crisp dialogue.",
233
+ "journal": "Write as personal journal entries from the protagonist's perspective. Intimate, reflective, and immediate.",
234
+ "chronicle": "Write as a historical chronicle, documenting events with a sense of importance and legacy. Slightly formal, sweeping in scope.",
235
+ };
236
+ return styles[style] || `Write the story in a ${style} style. Transform the game events into engaging narrative prose that captures the spirit of the adventure.`;
237
+ }
@@ -0,0 +1,41 @@
1
+ import type { Note } from "../types/index.js";
2
+ export declare function createNote(params: {
3
+ gameId: string;
4
+ title: string;
5
+ content: string;
6
+ category?: string;
7
+ relatedEntityId?: string;
8
+ relatedEntityType?: string;
9
+ tags?: string[];
10
+ pinned?: boolean;
11
+ }): Note;
12
+ export declare function getNote(id: string): Note | null;
13
+ export declare function updateNote(id: string, updates: {
14
+ title?: string;
15
+ content?: string;
16
+ category?: string | null;
17
+ relatedEntityId?: string | null;
18
+ relatedEntityType?: string | null;
19
+ tags?: string[];
20
+ }): Note | null;
21
+ export declare function deleteNote(id: string): boolean;
22
+ export declare function listNotes(gameId: string, filter?: {
23
+ category?: string;
24
+ pinned?: boolean;
25
+ tag?: string;
26
+ relatedEntityId?: string;
27
+ }): Note[];
28
+ export declare function searchNotes(gameId: string, query: string): Note[];
29
+ export declare function pinNote(id: string, pinned: boolean): Note | null;
30
+ /**
31
+ * Modify note tags - add and/or remove tags in a single call.
32
+ */
33
+ export declare function modifyNoteTags(id: string, params: {
34
+ add?: string[];
35
+ remove?: string[];
36
+ }): {
37
+ note: Note;
38
+ added: string[];
39
+ removed: string[];
40
+ } | null;
41
+ export declare function generateRecap(gameId: string, eventLimit?: number): Note;
@@ -0,0 +1,220 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { getDatabase } from "../db/connection.js";
3
+ import { safeJsonParse } from "../utils/json.js";
4
+ import { validateGameExists } from "./game.js";
5
+ import { getHistory } from "./narrative.js";
6
+ export function createNote(params) {
7
+ // Validate game exists to prevent orphaned records
8
+ validateGameExists(params.gameId);
9
+ const db = getDatabase();
10
+ const id = uuidv4();
11
+ const now = new Date().toISOString();
12
+ db.prepare(`
13
+ INSERT INTO notes (id, game_id, title, content, category, pinned, related_entity_id, related_entity_type, tags, created_at, updated_at)
14
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
15
+ `).run(id, params.gameId, params.title, params.content, params.category || null, params.pinned ? 1 : 0, params.relatedEntityId || null, params.relatedEntityType || null, JSON.stringify(params.tags || []), now, now);
16
+ return {
17
+ id,
18
+ gameId: params.gameId,
19
+ title: params.title,
20
+ content: params.content,
21
+ category: params.category || null,
22
+ pinned: params.pinned || false,
23
+ relatedEntityId: params.relatedEntityId || null,
24
+ relatedEntityType: params.relatedEntityType || null,
25
+ tags: params.tags || [],
26
+ createdAt: now,
27
+ updatedAt: now,
28
+ };
29
+ }
30
+ export function getNote(id) {
31
+ const db = getDatabase();
32
+ const row = db.prepare(`SELECT * FROM notes WHERE id = ?`).get(id);
33
+ if (!row)
34
+ return null;
35
+ return {
36
+ id: row.id,
37
+ gameId: row.game_id,
38
+ title: row.title,
39
+ content: row.content,
40
+ category: row.category,
41
+ pinned: row.pinned === 1,
42
+ relatedEntityId: row.related_entity_id,
43
+ relatedEntityType: row.related_entity_type,
44
+ tags: safeJsonParse(row.tags || "[]", []),
45
+ createdAt: row.created_at,
46
+ updatedAt: row.updated_at,
47
+ };
48
+ }
49
+ export function updateNote(id, updates) {
50
+ const db = getDatabase();
51
+ const current = getNote(id);
52
+ if (!current)
53
+ return null;
54
+ const now = new Date().toISOString();
55
+ const newTitle = updates.title ?? current.title;
56
+ const newContent = updates.content ?? current.content;
57
+ const newCategory = updates.category !== undefined ? updates.category : current.category;
58
+ const newRelatedId = updates.relatedEntityId !== undefined ? updates.relatedEntityId : current.relatedEntityId;
59
+ const newRelatedType = updates.relatedEntityType !== undefined ? updates.relatedEntityType : current.relatedEntityType;
60
+ const newTags = updates.tags ?? current.tags;
61
+ db.prepare(`
62
+ UPDATE notes
63
+ SET title = ?, content = ?, category = ?, related_entity_id = ?, related_entity_type = ?, tags = ?, updated_at = ?
64
+ WHERE id = ?
65
+ `).run(newTitle, newContent, newCategory, newRelatedId, newRelatedType, JSON.stringify(newTags), now, id);
66
+ return {
67
+ ...current,
68
+ title: newTitle,
69
+ content: newContent,
70
+ category: newCategory,
71
+ relatedEntityId: newRelatedId,
72
+ relatedEntityType: newRelatedType,
73
+ tags: newTags,
74
+ updatedAt: now,
75
+ };
76
+ }
77
+ export function deleteNote(id) {
78
+ const db = getDatabase();
79
+ const result = db.prepare(`DELETE FROM notes WHERE id = ?`).run(id);
80
+ return result.changes > 0;
81
+ }
82
+ export function listNotes(gameId, filter) {
83
+ const db = getDatabase();
84
+ let query = `SELECT * FROM notes WHERE game_id = ?`;
85
+ const params = [gameId];
86
+ if (filter?.category) {
87
+ query += ` AND category = ?`;
88
+ params.push(filter.category);
89
+ }
90
+ if (filter?.pinned !== undefined) {
91
+ query += ` AND pinned = ?`;
92
+ params.push(filter.pinned ? 1 : 0);
93
+ }
94
+ if (filter?.relatedEntityId) {
95
+ query += ` AND related_entity_id = ?`;
96
+ params.push(filter.relatedEntityId);
97
+ }
98
+ query += ` ORDER BY pinned DESC, updated_at DESC`;
99
+ let rows = db.prepare(query).all(...params);
100
+ // Filter by tag in memory (since it's a JSON array)
101
+ if (filter?.tag) {
102
+ const tagToMatch = filter.tag;
103
+ rows = rows.filter(row => {
104
+ const tags = safeJsonParse(row.tags || "[]", []);
105
+ return tags.includes(tagToMatch);
106
+ });
107
+ }
108
+ return rows.map(row => ({
109
+ id: row.id,
110
+ gameId: row.game_id,
111
+ title: row.title,
112
+ content: row.content,
113
+ category: row.category,
114
+ pinned: row.pinned === 1,
115
+ relatedEntityId: row.related_entity_id,
116
+ relatedEntityType: row.related_entity_type,
117
+ tags: safeJsonParse(row.tags || "[]", []),
118
+ createdAt: row.created_at,
119
+ updatedAt: row.updated_at,
120
+ }));
121
+ }
122
+ export function searchNotes(gameId, query) {
123
+ const db = getDatabase();
124
+ const searchPattern = `%${query}%`;
125
+ const rows = db.prepare(`
126
+ SELECT * FROM notes
127
+ WHERE game_id = ? AND (title LIKE ? OR content LIKE ?)
128
+ ORDER BY pinned DESC, updated_at DESC
129
+ `).all(gameId, searchPattern, searchPattern);
130
+ return rows.map(row => ({
131
+ id: row.id,
132
+ gameId: row.game_id,
133
+ title: row.title,
134
+ content: row.content,
135
+ category: row.category,
136
+ pinned: row.pinned === 1,
137
+ relatedEntityId: row.related_entity_id,
138
+ relatedEntityType: row.related_entity_type,
139
+ tags: safeJsonParse(row.tags || "[]", []),
140
+ createdAt: row.created_at,
141
+ updatedAt: row.updated_at,
142
+ }));
143
+ }
144
+ export function pinNote(id, pinned) {
145
+ const db = getDatabase();
146
+ const note = getNote(id);
147
+ if (!note)
148
+ return null;
149
+ const now = new Date().toISOString();
150
+ db.prepare(`UPDATE notes SET pinned = ?, updated_at = ? WHERE id = ?`)
151
+ .run(pinned ? 1 : 0, now, id);
152
+ return { ...note, pinned, updatedAt: now };
153
+ }
154
+ /**
155
+ * Modify note tags - add and/or remove tags in a single call.
156
+ */
157
+ export function modifyNoteTags(id, params) {
158
+ const db = getDatabase();
159
+ const note = getNote(id);
160
+ if (!note)
161
+ return null;
162
+ let tags = [...note.tags];
163
+ const added = [];
164
+ const removed = [];
165
+ // Remove tags first
166
+ if (params.remove) {
167
+ for (const tag of params.remove) {
168
+ if (tags.includes(tag)) {
169
+ tags = tags.filter(t => t !== tag);
170
+ removed.push(tag);
171
+ }
172
+ }
173
+ }
174
+ // Add new tags
175
+ if (params.add) {
176
+ for (const tag of params.add) {
177
+ if (!tags.includes(tag)) {
178
+ tags.push(tag);
179
+ added.push(tag);
180
+ }
181
+ }
182
+ }
183
+ const now = new Date().toISOString();
184
+ db.prepare(`UPDATE notes SET tags = ?, updated_at = ? WHERE id = ?`)
185
+ .run(JSON.stringify(tags), now, id);
186
+ return { note: { ...note, tags, updatedAt: now }, added, removed };
187
+ }
188
+ export function generateRecap(gameId, eventLimit = 20) {
189
+ // Get recent narrative events
190
+ const events = getHistory(gameId, { limit: eventLimit });
191
+ // Group by type for summary
192
+ const byType = {};
193
+ for (const event of events) {
194
+ if (!byType[event.eventType]) {
195
+ byType[event.eventType] = [];
196
+ }
197
+ byType[event.eventType].push(event.content);
198
+ }
199
+ // Build recap content
200
+ let content = "# Game Recap\n\n";
201
+ content += `*Generated from last ${events.length} events*\n\n`;
202
+ for (const [eventType, contents] of Object.entries(byType)) {
203
+ content += `## ${eventType.charAt(0).toUpperCase() + eventType.slice(1)}\n\n`;
204
+ for (const c of contents.slice(0, 5)) {
205
+ content += `- ${c.slice(0, 200)}${c.length > 200 ? "..." : ""}\n`;
206
+ }
207
+ if (contents.length > 5) {
208
+ content += `- *(and ${contents.length - 5} more...)*\n`;
209
+ }
210
+ content += "\n";
211
+ }
212
+ // Create the note
213
+ return createNote({
214
+ gameId,
215
+ title: `Game Recap - ${new Date().toLocaleDateString()}`,
216
+ content,
217
+ category: "recap",
218
+ tags: ["auto-generated", "recap"],
219
+ });
220
+ }
@@ -0,0 +1,110 @@
1
+ import type { PauseState, PauseChecklist, ResumeContext, NarrativeThread, ActiveConversation, ExternalUpdate, PendingUpdatesResult } from "../types/index.js";
2
+ /**
3
+ * Prepares for a game pause by returning current state, a comprehensive
4
+ * game state audit, persistence reminders, and a checklist of context
5
+ * that should be saved. This helps agents understand what needs to be
6
+ * captured before ending a game.
7
+ */
8
+ export declare function preparePause(gameId: string): PauseChecklist | null;
9
+ export interface SavePauseStateParams {
10
+ gameId: string;
11
+ currentScene: string;
12
+ immediateSituation: string;
13
+ sceneAtmosphere?: string;
14
+ pendingPlayerAction?: string;
15
+ awaitingResponseTo?: string;
16
+ presentedChoices?: string[];
17
+ activeThreads?: NarrativeThread[];
18
+ dmShortTermPlans?: string;
19
+ dmLongTermPlans?: string;
20
+ upcomingReveals?: string[];
21
+ npcAttitudes?: Record<string, string>;
22
+ activeConversations?: ActiveConversation[];
23
+ recentTone?: string;
24
+ playerApparentGoals?: string;
25
+ unresolvedHooks?: string[];
26
+ pauseReason?: string;
27
+ modelUsed?: string;
28
+ }
29
+ export declare function savePauseState(params: SavePauseStateParams): PauseState;
30
+ export declare function getPauseState(gameId: string): PauseState | null;
31
+ /**
32
+ * Returns everything needed to resume a paused game seamlessly.
33
+ * Includes pause state, full game state, and a ready-to-use resume prompt.
34
+ */
35
+ export declare function getResumeContext(gameId: string): ResumeContext | null;
36
+ export interface ContextSnapshotParams {
37
+ gameId: string;
38
+ situation: string;
39
+ notes?: string;
40
+ npcMood?: Record<string, string>;
41
+ playerIntent?: string;
42
+ }
43
+ /**
44
+ * Quick, lightweight context save for use during play.
45
+ * Agents should call this after significant moments.
46
+ * Updates only the most volatile fields without full pause ceremony.
47
+ */
48
+ export declare function saveContextSnapshot(params: ContextSnapshotParams): {
49
+ success: boolean;
50
+ message: string;
51
+ suggestion?: string;
52
+ };
53
+ export declare function deletePauseState(gameId: string): boolean;
54
+ /**
55
+ * Returns a reminder if context hasn't been saved recently.
56
+ * Use this to nudge agents to save context during long sessions.
57
+ */
58
+ export declare function checkContextFreshness(gameId: string): {
59
+ needsSave: boolean;
60
+ lastSaved: string | null;
61
+ minutesSinceLastSave: number | null;
62
+ suggestion: string;
63
+ };
64
+ export interface PushUpdateParams {
65
+ gameId: string;
66
+ sourceAgent: string;
67
+ sourceDescription?: string;
68
+ updateType: string;
69
+ category?: string;
70
+ title: string;
71
+ content: string;
72
+ structuredData?: Record<string, unknown>;
73
+ targetEntityId?: string;
74
+ targetEntityType?: string;
75
+ priority?: "low" | "normal" | "high" | "urgent";
76
+ }
77
+ /**
78
+ * Push an update from an external agent (research agent, worldbuilder, etc.)
79
+ * The primary DM agent will receive this update and can incorporate it.
80
+ */
81
+ export declare function pushExternalUpdate(params: PushUpdateParams): ExternalUpdate;
82
+ /**
83
+ * Get all pending updates for a game.
84
+ * Call this to check for new information from external agents.
85
+ */
86
+ export declare function getPendingUpdates(gameId: string): PendingUpdatesResult;
87
+ /**
88
+ * Acknowledge an update (mark as seen by DM).
89
+ */
90
+ export declare function acknowledgeUpdate(updateId: string): ExternalUpdate | null;
91
+ /**
92
+ * Mark an update as applied (incorporated into the narrative).
93
+ */
94
+ export declare function applyUpdate(updateId: string, dmNotes?: string): ExternalUpdate | null;
95
+ /**
96
+ * Reject an update (not appropriate for the narrative).
97
+ */
98
+ export declare function rejectUpdate(updateId: string, dmNotes?: string): ExternalUpdate | null;
99
+ /**
100
+ * Get a specific external update by ID.
101
+ */
102
+ export declare function getExternalUpdate(updateId: string): ExternalUpdate | null;
103
+ /**
104
+ * List all updates for a game with optional status filter.
105
+ */
106
+ export declare function listExternalUpdates(gameId: string, status?: ExternalUpdate["status"]): ExternalUpdate[];
107
+ /**
108
+ * Delete an external update.
109
+ */
110
+ export declare function deleteExternalUpdate(updateId: string): boolean;