@exaudeus/memory-mcp 1.4.0 → 1.5.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/README.md +4 -1
- package/dist/formatters.d.ts +3 -1
- package/dist/formatters.js +11 -2
- package/dist/index.js +40 -17
- package/dist/store.d.ts +2 -2
- package/dist/store.js +24 -10
- package/dist/types.d.ts +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ A Model Context Protocol (MCP) server that gives AI coding agents persistent, ev
|
|
|
8
8
|
|------|-------------|
|
|
9
9
|
| `memory_context` | Session start AND pre-task lookup. Call with no args for user + preferences + stale nudges; call with `context` for task-specific knowledge |
|
|
10
10
|
| `memory_query` | Structured search with brief/standard/full detail levels and AND/OR/NOT filter syntax. Scope defaults to `"*"` (all topics) |
|
|
11
|
-
| `memory_store` | Store a knowledge entry with dedup detection, preference surfacing,
|
|
11
|
+
| `memory_store` | Store a knowledge entry with dedup detection, preference surfacing, lobe auto-detection, and a review-required gate for likely-ephemeral content |
|
|
12
12
|
| `memory_correct` | Correct, update, or delete an existing entry (suggests storing as preference) |
|
|
13
13
|
| `memory_bootstrap` | First-use scan to seed knowledge from repo structure, README, and build files |
|
|
14
14
|
|
|
@@ -33,6 +33,7 @@ A Model Context Protocol (MCP) server that gives AI coding agents persistent, ev
|
|
|
33
33
|
|
|
34
34
|
- **Dedup detection**: When you store an entry, the response shows similar existing entries in the same topic (>35% keyword overlap) with consolidation instructions
|
|
35
35
|
- **Preference surfacing**: Storing a non-preference entry shows relevant preferences that might conflict
|
|
36
|
+
- **Ephemeral review gate**: Likely-ephemeral content is blocked before persistence by default. Re-run `memory_store(..., durabilityDecision: "store-anyway")` only when you intentionally want to keep it.
|
|
36
37
|
- **Piggyback hints**: `memory_correct` suggests storing corrections as reusable preferences
|
|
37
38
|
- **`memory_context`**: Describe your task in natural language and get ranked results across all topics with topic-based boosting (preferences 1.8x, gotchas 1.5x)
|
|
38
39
|
|
|
@@ -108,10 +109,12 @@ If no `memory-config.json` is found, the server falls back to environment variab
|
|
|
108
109
|
1. **Edit `memory-config.json`** (create if it doesn't exist)
|
|
109
110
|
2. **Add lobe entry:**
|
|
110
111
|
```json
|
|
112
|
+
{
|
|
111
113
|
"my-project": {
|
|
112
114
|
"root": "$HOME/git/my-project",
|
|
113
115
|
"budgetMB": 2
|
|
114
116
|
}
|
|
117
|
+
}
|
|
115
118
|
```
|
|
116
119
|
3. **Restart the memory MCP server**
|
|
117
120
|
4. **Verify:** Use `memory_list_lobes` to confirm it loaded
|
package/dist/formatters.d.ts
CHANGED
|
@@ -22,4 +22,6 @@ export declare function buildQueryFooter(opts: {
|
|
|
22
22
|
readonly scope: string;
|
|
23
23
|
}): string;
|
|
24
24
|
/** Build tag primer section for session briefing — pure function */
|
|
25
|
-
export declare function buildTagPrimerSection(tagFreq: ReadonlyMap<string, number
|
|
25
|
+
export declare function buildTagPrimerSection(tagFreq: ReadonlyMap<string, number>, lobeName?: string): string;
|
|
26
|
+
/** Build briefing tag primer sections without merging vocabularies across lobes. */
|
|
27
|
+
export declare function buildBriefingTagPrimerSections(lobeTagFrequencies: Iterable<readonly [string, ReadonlyMap<string, number>]>): readonly string[];
|
package/dist/formatters.js
CHANGED
|
@@ -173,7 +173,7 @@ export function buildQueryFooter(opts) {
|
|
|
173
173
|
return lines.join('\n');
|
|
174
174
|
}
|
|
175
175
|
/** Build tag primer section for session briefing — pure function */
|
|
176
|
-
export function buildTagPrimerSection(tagFreq) {
|
|
176
|
+
export function buildTagPrimerSection(tagFreq, lobeName) {
|
|
177
177
|
if (tagFreq.size === 0)
|
|
178
178
|
return '';
|
|
179
179
|
const allTags = [...tagFreq.entries()]
|
|
@@ -181,7 +181,9 @@ export function buildTagPrimerSection(tagFreq) {
|
|
|
181
181
|
.map(([tag, count]) => `${tag}(${count})`)
|
|
182
182
|
.join(', ');
|
|
183
183
|
return [
|
|
184
|
-
|
|
184
|
+
lobeName
|
|
185
|
+
? `### Tag Vocabulary — ${lobeName} (${tagFreq.size} tags)`
|
|
186
|
+
: `### Tag Vocabulary (${tagFreq.size} tags)`,
|
|
185
187
|
allTags,
|
|
186
188
|
``,
|
|
187
189
|
`Filter by tags: memory_query(filter: "#auth") — exact match`,
|
|
@@ -189,3 +191,10 @@ export function buildTagPrimerSection(tagFreq) {
|
|
|
189
191
|
`Multiple: memory_query(filter: "#auth|#security") — OR logic`,
|
|
190
192
|
].join('\n');
|
|
191
193
|
}
|
|
194
|
+
/** Build briefing tag primer sections without merging vocabularies across lobes. */
|
|
195
|
+
export function buildBriefingTagPrimerSections(lobeTagFrequencies) {
|
|
196
|
+
const nonEmpty = Array.from(lobeTagFrequencies)
|
|
197
|
+
.filter(([, tagFreq]) => tagFreq.size > 0);
|
|
198
|
+
const includeLobeNames = nonEmpty.length > 1;
|
|
199
|
+
return nonEmpty.map(([lobeName, tagFreq]) => buildTagPrimerSection(tagFreq, includeLobeNames ? lobeName : undefined));
|
|
200
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { getLobeConfigs } from './config.js';
|
|
|
14
14
|
import { ConfigManager } from './config-manager.js';
|
|
15
15
|
import { normalizeArgs } from './normalize.js';
|
|
16
16
|
import { buildCrashReport, writeCrashReport, writeCrashReportSync, readLatestCrash, readCrashHistory, clearLatestCrash, formatCrashReport, formatCrashSummary, markServerStarted, } from './crash-journal.js';
|
|
17
|
-
import { formatStaleSection, formatConflictWarning, formatStats, formatBehaviorConfigSection, mergeTagFrequencies, buildQueryFooter,
|
|
17
|
+
import { formatStaleSection, formatConflictWarning, formatStats, formatBehaviorConfigSection, mergeTagFrequencies, buildQueryFooter, buildBriefingTagPrimerSections } from './formatters.js';
|
|
18
18
|
import { parseFilter } from './text-analyzer.js';
|
|
19
19
|
import { VOCABULARY_ECHO_LIMIT, WARN_SEPARATOR } from './thresholds.js';
|
|
20
20
|
import { matchRootsToLobeNames, buildLobeResolution } from './lobe-resolution.js';
|
|
@@ -215,7 +215,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
215
215
|
// Example comes first — agents form their call shape from the first concrete pattern they see.
|
|
216
216
|
// "entries" (not "content") signals a collection; fighting the "content = string" prior
|
|
217
217
|
// is an architectural fix rather than patching the description after the fact.
|
|
218
|
-
description: 'memory_store(topic: "gotchas", entries: [{title: "Build cache", fact: "Must clean build after Tuist changes"}, {title: "Tuist version", fact: "Project requires Tuist 4.x"}], tags: ["build"]). Stores enduring knowledge — (1) Codebase facts (architecture, conventions, gotchas, modules): what IS true now, not past actions. Wrong: "Completed migration to StateFlow." Right: "All ViewModels use StateFlow." (2) User knowledge (user, preferences): who the person is, how they work. "user" and "preferences" are stored in the alwaysInclude lobe (shared across projects). One insight per object; use multiple objects instead of bundling.',
|
|
218
|
+
description: 'memory_store(topic: "gotchas", entries: [{title: "Build cache", fact: "Must clean build after Tuist changes"}, {title: "Tuist version", fact: "Project requires Tuist 4.x"}], tags: ["build"]). Stores enduring knowledge — (1) Codebase facts (architecture, conventions, gotchas, modules): what IS true now, not past actions. Wrong: "Completed migration to StateFlow." Right: "All ViewModels use StateFlow." (2) User knowledge (user, preferences): who the person is, how they work. "user" and "preferences" are stored in the alwaysInclude lobe (shared across projects). One insight per object; use multiple objects instead of bundling. If content looks likely-ephemeral, the tool returns a review-required response; re-run with durabilityDecision: "store-anyway" only when deliberate.',
|
|
219
219
|
inputSchema: {
|
|
220
220
|
type: 'object',
|
|
221
221
|
properties: {
|
|
@@ -272,6 +272,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
272
272
|
description: 'Category labels for exact-match retrieval (lowercase slugs). Query with filter: "#tag". Example: ["auth", "critical-path", "mite-combat"]',
|
|
273
273
|
default: [],
|
|
274
274
|
},
|
|
275
|
+
durabilityDecision: {
|
|
276
|
+
type: 'string',
|
|
277
|
+
enum: ['default', 'store-anyway'],
|
|
278
|
+
description: 'Write intent for content that may require review. Use "default" normally. Use "store-anyway" only when intentionally persisting content after a review-required response.',
|
|
279
|
+
default: 'default',
|
|
280
|
+
},
|
|
275
281
|
},
|
|
276
282
|
required: ['topic', 'entries'],
|
|
277
283
|
},
|
|
@@ -442,7 +448,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
442
448
|
};
|
|
443
449
|
}
|
|
444
450
|
case 'memory_store': {
|
|
445
|
-
const { lobe: rawLobe, topic: rawTopic, entries: rawEntries, sources, references, trust: rawTrust, tags: rawTags } = z.object({
|
|
451
|
+
const { lobe: rawLobe, topic: rawTopic, entries: rawEntries, sources, references, trust: rawTrust, tags: rawTags, durabilityDecision } = z.object({
|
|
446
452
|
lobe: z.string().optional(),
|
|
447
453
|
topic: z.string(),
|
|
448
454
|
// Accept a bare {title, fact} object in addition to the canonical array form.
|
|
@@ -456,6 +462,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
456
462
|
references: z.array(z.string()).default([]),
|
|
457
463
|
trust: z.enum(['user', 'agent-confirmed', 'agent-inferred']).default('agent-inferred'),
|
|
458
464
|
tags: z.array(z.string()).default([]),
|
|
465
|
+
durabilityDecision: z.enum(['default', 'store-anyway']).default('default'),
|
|
459
466
|
}).parse(args);
|
|
460
467
|
// Validate topic at boundary
|
|
461
468
|
const topic = parseTopicScope(rawTopic);
|
|
@@ -494,8 +501,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
494
501
|
: trust;
|
|
495
502
|
const storedResults = [];
|
|
496
503
|
for (const { title, fact } of rawEntries) {
|
|
497
|
-
const result = await ctx.store.store(topic, title, fact, sources, effectiveTrust, references, rawTags);
|
|
498
|
-
if (
|
|
504
|
+
const result = await ctx.store.store(topic, title, fact, sources, effectiveTrust, references, rawTags, durabilityDecision);
|
|
505
|
+
if (result.kind === 'review-required') {
|
|
506
|
+
const lines = [
|
|
507
|
+
`[${ctx.label}] Review required before storing "${title}".`,
|
|
508
|
+
'',
|
|
509
|
+
`Severity: ${result.severity}`,
|
|
510
|
+
'Signals:',
|
|
511
|
+
...result.signals.map(signal => `- ${signal.label}: ${signal.detail}`),
|
|
512
|
+
'',
|
|
513
|
+
result.warning,
|
|
514
|
+
'',
|
|
515
|
+
'If this knowledge is still worth persisting, re-run with:',
|
|
516
|
+
`memory_store(topic: "${topic}", entries: [{title: "${title}", fact: "${fact.replace(/"/g, '\\"')}"}], trust: "${effectiveTrust}", durabilityDecision: "store-anyway"${rawTags.length > 0 ? `, tags: ${JSON.stringify(rawTags)}` : ''}${sources.length > 0 ? `, sources: ${JSON.stringify(sources)}` : ''}${references.length > 0 ? `, references: ${JSON.stringify(references)}` : ''})`,
|
|
517
|
+
];
|
|
518
|
+
return {
|
|
519
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
520
|
+
isError: true,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (result.kind === 'rejected') {
|
|
499
524
|
return {
|
|
500
525
|
content: [{ type: 'text', text: `[${ctx.label}] Failed to store "${title}": ${result.warning}` }],
|
|
501
526
|
isError: true,
|
|
@@ -861,17 +886,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
861
886
|
if (sections.length === 0) {
|
|
862
887
|
sections.push('No knowledge stored yet. As you work, store observations with memory_store. Try memory_bootstrap to seed initial knowledge from the repo.');
|
|
863
888
|
}
|
|
864
|
-
// Tag primer:
|
|
865
|
-
const
|
|
866
|
-
|
|
889
|
+
// Tag primer: keep vocabularies lobe-local instead of merging them across lobes.
|
|
890
|
+
const briefingTagPrimers = buildBriefingTagPrimerSections(allBriefingLobeNames
|
|
891
|
+
.filter(lobeName => configManager.getLobeHealth(lobeName)?.status !== 'degraded')
|
|
892
|
+
.map((lobeName) => {
|
|
867
893
|
const store = configManager.getStore(lobeName);
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const tagPrimer = buildTagPrimerSection(briefingTagFreq);
|
|
873
|
-
if (tagPrimer) {
|
|
874
|
-
sections.push(tagPrimer);
|
|
894
|
+
return [lobeName, store?.getTagFrequency() ?? new Map()];
|
|
895
|
+
}));
|
|
896
|
+
if (briefingTagPrimers.length > 0) {
|
|
897
|
+
sections.push(...briefingTagPrimers);
|
|
875
898
|
}
|
|
876
899
|
const briefingHints = [];
|
|
877
900
|
briefingHints.push(`${totalEntries} entries${totalStale > 0 ? ` (${totalStale} stale)` : ''} across ${allBriefingLobeNames.length} ${allBriefingLobeNames.length === 1 ? 'lobe' : 'lobes'}.`);
|
|
@@ -1092,8 +1115,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1092
1115
|
if (!ctx.ok)
|
|
1093
1116
|
return contextError(ctx);
|
|
1094
1117
|
const results = await ctx.store.bootstrap();
|
|
1095
|
-
const stored = results.filter(r => r.stored);
|
|
1096
|
-
const failed = results.filter(r =>
|
|
1118
|
+
const stored = results.filter((r) => r.kind === 'stored');
|
|
1119
|
+
const failed = results.filter((r) => r.kind !== 'stored');
|
|
1097
1120
|
let text = `## [${ctx.label}] Bootstrap Complete\n\nStored ${stored.length} entries:`;
|
|
1098
1121
|
for (const r of stored) {
|
|
1099
1122
|
text += `\n- ${r.id}: ${r.topic} (${r.file})`;
|
package/dist/store.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MemoryEntry, TopicScope, TrustLevel, DetailLevel, QueryResult, StoreResult, CorrectResult, MemoryStats, BriefingResult, ConflictPair, MemoryConfig } from './types.js';
|
|
1
|
+
import type { MemoryEntry, TopicScope, TrustLevel, DetailLevel, DurabilityDecision, QueryResult, StoreResult, CorrectResult, MemoryStats, BriefingResult, ConflictPair, MemoryConfig } from './types.js';
|
|
2
2
|
export declare class MarkdownMemoryStore {
|
|
3
3
|
private readonly config;
|
|
4
4
|
private readonly memoryPath;
|
|
@@ -13,7 +13,7 @@ export declare class MarkdownMemoryStore {
|
|
|
13
13
|
/** Initialize the store: create memory dir and load existing entries */
|
|
14
14
|
init(): Promise<void>;
|
|
15
15
|
/** Store a new knowledge entry */
|
|
16
|
-
store(topic: TopicScope, title: string, content: string, sources?: string[], trust?: TrustLevel, references?: string[], rawTags?: string[]): Promise<StoreResult>;
|
|
16
|
+
store(topic: TopicScope, title: string, content: string, sources?: string[], trust?: TrustLevel, references?: string[], rawTags?: string[], durabilityDecision?: DurabilityDecision): Promise<StoreResult>;
|
|
17
17
|
/** Query knowledge by scope and detail level */
|
|
18
18
|
query(scope: string, detail?: DetailLevel, filter?: string, branchFilter?: string): Promise<QueryResult>;
|
|
19
19
|
/** Generate a session-start briefing */
|
package/dist/store.js
CHANGED
|
@@ -41,15 +41,35 @@ export class MarkdownMemoryStore {
|
|
|
41
41
|
await this.reloadFromDisk();
|
|
42
42
|
}
|
|
43
43
|
/** Store a new knowledge entry */
|
|
44
|
-
async store(topic, title, content, sources = [], trust = 'agent-inferred', references = [], rawTags = []) {
|
|
44
|
+
async store(topic, title, content, sources = [], trust = 'agent-inferred', references = [], rawTags = [], durabilityDecision = 'default') {
|
|
45
45
|
// Check storage budget — null means we can't measure, allow the write
|
|
46
46
|
const currentSize = await this.getStorageSize();
|
|
47
47
|
if (currentSize !== null && currentSize >= this.config.storageBudgetBytes) {
|
|
48
48
|
return {
|
|
49
|
-
stored: false, topic,
|
|
49
|
+
kind: 'rejected', stored: false, topic,
|
|
50
50
|
warning: `Storage budget exceeded (${this.formatBytes(currentSize)} / ${this.formatBytes(this.config.storageBudgetBytes)}). Delete or correct existing entries to free space.`,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
+
const ephemeralSignals = topic !== 'recent-work'
|
|
54
|
+
? detectEphemeralSignals(title, content, topic)
|
|
55
|
+
: [];
|
|
56
|
+
const ephemeralSeverity = getEphemeralSeverity(ephemeralSignals);
|
|
57
|
+
const requiresReview = ephemeralSeverity === 'medium' || ephemeralSeverity === 'high';
|
|
58
|
+
if (durabilityDecision === 'default' && requiresReview) {
|
|
59
|
+
return {
|
|
60
|
+
kind: 'review-required',
|
|
61
|
+
stored: false,
|
|
62
|
+
topic,
|
|
63
|
+
severity: ephemeralSeverity,
|
|
64
|
+
warning: `Likely ephemeral content detected. Review the signals and re-run with durabilityDecision: "store-anyway" if this knowledge should still be persisted.`,
|
|
65
|
+
signals: ephemeralSignals.map(signal => ({
|
|
66
|
+
id: signal.id,
|
|
67
|
+
label: signal.label,
|
|
68
|
+
detail: signal.detail,
|
|
69
|
+
confidence: signal.confidence,
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
53
73
|
const id = this.generateId(topic);
|
|
54
74
|
const now = this.clock.isoNow();
|
|
55
75
|
const confidence = DEFAULT_CONFIDENCE[trust];
|
|
@@ -85,15 +105,9 @@ export class MarkdownMemoryStore {
|
|
|
85
105
|
const relevantPreferences = (topic !== 'preferences' && topic !== 'user')
|
|
86
106
|
? this.findRelevantPreferences(entry)
|
|
87
107
|
: undefined;
|
|
88
|
-
// Soft ephemeral detection — warn but never block
|
|
89
|
-
const ephemeralSignals = topic !== 'recent-work'
|
|
90
|
-
? detectEphemeralSignals(title, content, topic)
|
|
91
|
-
: [];
|
|
92
|
-
// getEphemeralSeverity is the single source of threshold logic shared with formatEphemeralWarning.
|
|
93
|
-
const ephemeralSeverity = getEphemeralSeverity(ephemeralSignals);
|
|
94
108
|
const ephemeralWarning = formatEphemeralWarning(ephemeralSignals, id);
|
|
95
109
|
return {
|
|
96
|
-
stored: true, id, topic, file, confidence, warning, ephemeralWarning,
|
|
110
|
+
kind: 'stored', stored: true, id, topic, file, confidence, warning, ephemeralWarning,
|
|
97
111
|
ephemeralSeverity: ephemeralSeverity ?? undefined,
|
|
98
112
|
relatedEntries: relatedEntries.length > 0 ? relatedEntries : undefined,
|
|
99
113
|
relevantPreferences: relevantPreferences && relevantPreferences.length > 0 ? relevantPreferences : undefined,
|
|
@@ -384,7 +398,7 @@ export class MarkdownMemoryStore {
|
|
|
384
398
|
catch { /* not a git repo or git not available */ }
|
|
385
399
|
// 5. Fallback: if nothing was detected, store a minimal overview so the lobe
|
|
386
400
|
// is never left completely empty after bootstrap (makes memory_context useful immediately).
|
|
387
|
-
const storedCount = results.filter(r => r.stored).length;
|
|
401
|
+
const storedCount = results.filter(r => r.kind === 'stored').length;
|
|
388
402
|
if (storedCount === 0) {
|
|
389
403
|
try {
|
|
390
404
|
const topLevel = await fs.readdir(repoRoot, { withFileTypes: true });
|
package/dist/types.d.ts
CHANGED
|
@@ -89,8 +89,21 @@ export interface RelatedEntry {
|
|
|
89
89
|
readonly confidence: number;
|
|
90
90
|
readonly trust: TrustLevel;
|
|
91
91
|
}
|
|
92
|
+
/** Caller intent for writes that may require a durability decision.
|
|
93
|
+
* Explicit domain type avoids booleanly-typed override semantics. */
|
|
94
|
+
export type DurabilityDecision = 'default' | 'store-anyway';
|
|
95
|
+
/** Structured signal attached to a review-required store outcome.
|
|
96
|
+
* Duplicated here (rather than importing from ephemeral.ts) to keep the store
|
|
97
|
+
* result contract independent of a concrete detection implementation. */
|
|
98
|
+
export interface ReviewSignal {
|
|
99
|
+
readonly id: string;
|
|
100
|
+
readonly label: string;
|
|
101
|
+
readonly detail: string;
|
|
102
|
+
readonly confidence: 'high' | 'medium' | 'low';
|
|
103
|
+
}
|
|
92
104
|
/** Result of a memory store operation — discriminated union eliminates impossible states */
|
|
93
105
|
export type StoreResult = {
|
|
106
|
+
readonly kind: 'stored';
|
|
94
107
|
readonly stored: true;
|
|
95
108
|
readonly id: string;
|
|
96
109
|
readonly topic: TopicScope;
|
|
@@ -104,6 +117,14 @@ export type StoreResult = {
|
|
|
104
117
|
readonly relatedEntries?: readonly RelatedEntry[];
|
|
105
118
|
readonly relevantPreferences?: readonly RelatedEntry[];
|
|
106
119
|
} | {
|
|
120
|
+
readonly kind: 'review-required';
|
|
121
|
+
readonly stored: false;
|
|
122
|
+
readonly topic: TopicScope;
|
|
123
|
+
readonly severity: EphemeralSeverity;
|
|
124
|
+
readonly warning: string;
|
|
125
|
+
readonly signals: readonly ReviewSignal[];
|
|
126
|
+
} | {
|
|
127
|
+
readonly kind: 'rejected';
|
|
107
128
|
readonly stored: false;
|
|
108
129
|
readonly topic: TopicScope;
|
|
109
130
|
readonly warning: string;
|