@attentionform/astra-mcp-server 0.0.0-stage → 0.2.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.
- package/LICENSE +206 -0
- package/NOTICE +12 -0
- package/README.md +500 -2
- package/dist/index.d.ts +63 -0
- package/dist/index.js +43889 -0
- package/dist/lib/index.d.ts +10 -0
- package/dist/lib/mcp/baseline-gate.d.ts +71 -0
- package/dist/lib/mcp/descriptions.d.ts +12 -0
- package/dist/lib/mcp/diff.d.ts +220 -0
- package/dist/lib/mcp/errors.d.ts +45 -0
- package/dist/lib/mcp/handlers/export-audit.d.ts +34 -0
- package/dist/lib/mcp/handlers/history.d.ts +93 -0
- package/dist/lib/mcp/handlers/open-inspect.d.ts +71 -0
- package/dist/lib/mcp/handlers/parity.d.ts +127 -0
- package/dist/lib/mcp/handlers/save-close.d.ts +96 -0
- package/dist/lib/mcp/handlers/search-batch.d.ts +55 -0
- package/dist/lib/mcp/normalize-read.d.ts +11 -0
- package/dist/lib/mcp/registry.d.ts +12 -0
- package/dist/lib/mcp/schema-dump.d.ts +24 -0
- package/dist/lib/mcp/schemas.d.ts +953 -0
- package/dist/lib/mcp/tool-names.d.ts +15 -0
- package/dist/lib/mcp-tool-adapter.d.ts +12 -0
- package/dist/lib/session-store.d.ts +432 -0
- package/dist/tools/compose-document.d.ts +124 -0
- package/dist/tools/export.d.ts +53 -0
- package/dist/tools/new-document.d.ts +53 -0
- package/dist/tools/visual.d.ts +161 -0
- package/package.json +69 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const MCP_RESEARCH_FLAG = "--with-research";
|
|
2
|
+
/** Legacy helper: reports argv intent only; server startup accepts no such flag. */
|
|
3
|
+
export declare function isResearchEnabled(argv?: readonly string[]): boolean;
|
|
4
|
+
/** Lean-6 tool names (locked, no single_op). */
|
|
5
|
+
export declare const LEAN6_TOOL_NAMES: readonly ["open_document", "inspect_document", "search_content", "execute_batch", "save_document", "close_document"];
|
|
6
|
+
export type Lean6ToolName = (typeof LEAN6_TOOL_NAMES)[number];
|
|
7
|
+
export declare function getLean6ToolNames(): Lean6ToolName[];
|
|
8
|
+
/** Git-history tool names (history browsing + preview + revert + redo; registered alongside lean-6). */
|
|
9
|
+
export declare const GIT_HISTORY_TOOL_NAMES: readonly ["get_document_history", "preview_revert", "revert_document", "redo_document"];
|
|
10
|
+
export type GitHistoryToolName = (typeof GIT_HISTORY_TOOL_NAMES)[number];
|
|
11
|
+
export declare function getGitHistoryToolNames(): GitHistoryToolName[];
|
|
12
|
+
/** Document-audit tool names (read-only preflight + census + scrub + canonical repair + file ingest). */
|
|
13
|
+
export declare const PARITY_TOOL_NAMES: readonly ["validate_document", "get_stats", "sanitize_document", "normalize_document", "import_document"];
|
|
14
|
+
export type ParityToolName = (typeof PARITY_TOOL_NAMES)[number];
|
|
15
|
+
export declare function getParityToolNames(): ParityToolName[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './mcp/normalize-read';
|
|
2
|
+
export * from './mcp/tool-names';
|
|
3
|
+
export * from './mcp/errors';
|
|
4
|
+
export * from './mcp/diff';
|
|
5
|
+
export * from './mcp/descriptions';
|
|
6
|
+
export * from './mcp/schemas';
|
|
7
|
+
export * from './mcp/handlers/open-inspect';
|
|
8
|
+
export * from './mcp/handlers/search-batch';
|
|
9
|
+
export * from './mcp/handlers/save-close';
|
|
10
|
+
export * from './mcp/handlers/history';
|
|
11
|
+
export * from './mcp/handlers/export-audit';
|
|
12
|
+
export * from './mcp/registry';
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { type DocumentAST, type DocxParserMacroOptions, DocxParser, OperationEngine, ToonGenerator, canonicalizeForAsset as canonicalizeForMcp, type StyleGuideConfig, type ValidationBaseline } from '@attentionform/astra-engine';
|
|
2
|
+
/**
|
|
3
|
+
* Sprint-2 Muse session store (MCP Lean-6 backing).
|
|
4
|
+
*
|
|
5
|
+
* Sessions: Map<sessionId,DocHandle> (multi-doc; replaces the single active-document holder).
|
|
6
|
+
* - LRU 10 docs, 200MB total cap, idle 30min evict.
|
|
7
|
+
* - Per-session revision (bumped on every successful execute_batch).
|
|
8
|
+
* - Per-session mutex (each DocHandle owns its lock; no shared global lock).
|
|
9
|
+
*
|
|
10
|
+
* There is intentionally no shared module-level instance here:
|
|
11
|
+
* callers construct their own store via createMcpSessionStore().
|
|
12
|
+
*/
|
|
13
|
+
export declare const MCP_SESSION_MAX_DOCS = 10;
|
|
14
|
+
export declare const MCP_SESSION_MAX_BYTES: number;
|
|
15
|
+
export declare const MCP_SESSION_IDLE_MS: number;
|
|
16
|
+
/**
|
|
17
|
+
* Redo-stack quotas (per session, in-memory). High defaults so normal
|
|
18
|
+
* revert/redo chains (1-5 entries, KBs) never hit them; only runaway
|
|
19
|
+
* rapid-revert loops do. Configurable via store options or env.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SESSION_REDO_MAX_DEPTH = 100;
|
|
22
|
+
export declare const SESSION_REDO_MAX_BYTES: number;
|
|
23
|
+
/** Env overrides for the redo quotas (positive integers, bytes for the latter). */
|
|
24
|
+
export declare const SESSION_REDO_MAX_DEPTH_ENV = "ASTRA_REDO_MAX_DEPTH";
|
|
25
|
+
export declare const SESSION_REDO_MAX_BYTES_ENV = "ASTRA_REDO_MAX_BYTES";
|
|
26
|
+
/** L1 upper clamps: env/explicit values above these are capped (env-driven memory exhaustion). */
|
|
27
|
+
export declare const SESSION_REDO_MAX_DEPTH_CLAMP = 10000;
|
|
28
|
+
export declare const SESSION_REDO_MAX_BYTES_CLAMP: number;
|
|
29
|
+
/** Error code for quota refusals (shared with history sidecar caps). */
|
|
30
|
+
export declare const SESSION_QUOTA_CODE: "E_QUOTA";
|
|
31
|
+
/** Prefix for every server-minted session id (`sess_<uuid>`). */
|
|
32
|
+
export declare const MCP_SESSION_ID_PREFIX = "sess_";
|
|
33
|
+
/** CLI flag enabling deterministic client-supplied ids (tests only, default off). */
|
|
34
|
+
export declare const CLIENT_SESSION_ID_FLAG = "--allow-client-session-ids";
|
|
35
|
+
/** Env var mirroring the flag (accepted values: 1/true, case-insensitive). */
|
|
36
|
+
export declare const CLIENT_SESSION_ID_ENV = "ASTRA_ALLOW_CLIENT_SESSION_IDS";
|
|
37
|
+
export interface McpSessionStoreOptions {
|
|
38
|
+
maxDocs?: number;
|
|
39
|
+
maxBytes?: number;
|
|
40
|
+
idleMs?: number;
|
|
41
|
+
/** Redo-stack depth cap (entries). Defaults to SESSION_REDO_MAX_DEPTH / env. */
|
|
42
|
+
redoMaxDepth?: number;
|
|
43
|
+
/** Redo-stack bytes cap (sum of snapshot bytes). Defaults to SESSION_REDO_MAX_BYTES / env. */
|
|
44
|
+
redoMaxBytes?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Hatch for deterministic ids in tests. Default off: client-supplied
|
|
47
|
+
* sessionIds are rejected with E_VALIDATION and every open mints
|
|
48
|
+
* `sess_<uuid>`. Also enabled via --allow-client-session-ids /
|
|
49
|
+
* ASTRA_ALLOW_CLIENT_SESSION_IDS.
|
|
50
|
+
*/
|
|
51
|
+
allowClientSessionIds?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** True when the deterministic-id hatch is enabled via env. */
|
|
54
|
+
export declare function isClientSessionIdHatchEnabledByEnv(env?: NodeJS.ProcessEnv): boolean;
|
|
55
|
+
/** True when argv carries --allow-client-session-ids before any `--` marker. */
|
|
56
|
+
export declare function hasClientSessionIdHatch(argv?: readonly string[]): boolean;
|
|
57
|
+
/** Client-facing rejection for a squatted sessionId (E_VALIDATION + remedy). */
|
|
58
|
+
export declare function buildClientSessionIdRejection(tool: string, raw: unknown): {
|
|
59
|
+
code: 'E_VALIDATION';
|
|
60
|
+
message: string;
|
|
61
|
+
remedy: string;
|
|
62
|
+
};
|
|
63
|
+
/** Resolves the redo depth cap (explicit > env > default). Never below 1. L1: clamped to SESSION_REDO_MAX_DEPTH_CLAMP. */
|
|
64
|
+
export declare function resolveRedoMaxDepth(explicit?: number, env?: NodeJS.ProcessEnv): number;
|
|
65
|
+
/** Resolves the redo bytes cap (explicit > env > default). Never below 1. L1: clamped to SESSION_REDO_MAX_BYTES_CLAMP. */
|
|
66
|
+
export declare function resolveRedoMaxBytes(explicit?: number, env?: NodeJS.ProcessEnv): number;
|
|
67
|
+
/** Byte size of one package snapshot (sum of part lengths). */
|
|
68
|
+
export declare function redoSnapshotByteSize(snap: Map<string, Buffer>): number;
|
|
69
|
+
/** Per-session async mutex (promise-chain, no global lock). */
|
|
70
|
+
export declare class SessionMutex {
|
|
71
|
+
private tail;
|
|
72
|
+
private inflight;
|
|
73
|
+
runExclusive<T>(fn: () => Promise<T>): Promise<T>;
|
|
74
|
+
/**
|
|
75
|
+
* M1: real locked state (queued + running).
|
|
76
|
+
* True while any runExclusive section is queued or holding the lock.
|
|
77
|
+
* Used by closeDocument to fail closed with E_CONFLICT instead of racing
|
|
78
|
+
* a concurrent batch.
|
|
79
|
+
*/
|
|
80
|
+
isLocked(): boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface DocHandle {
|
|
83
|
+
sessionId: string;
|
|
84
|
+
filePath: string;
|
|
85
|
+
parser: DocxParser;
|
|
86
|
+
ast: DocumentAST;
|
|
87
|
+
engine: OperationEngine;
|
|
88
|
+
toon: ToonGenerator;
|
|
89
|
+
revision: number;
|
|
90
|
+
/**
|
|
91
|
+
* Phase C: revision at the last successful save_document. dirty =
|
|
92
|
+
* revision !== savedRevision (in-memory session holds unsaved
|
|
93
|
+
* execute_batch edits vs the last file written to disk). Set to 1 on
|
|
94
|
+
* open, re-synced on every successful save.
|
|
95
|
+
*/
|
|
96
|
+
savedRevision: number;
|
|
97
|
+
byteSize: number;
|
|
98
|
+
lastAccess: number;
|
|
99
|
+
mutex: SessionMutex;
|
|
100
|
+
/**
|
|
101
|
+
* History redo stack: full package snapshots captured before each
|
|
102
|
+
* revert_document restore (newest last). redo_document pops one entry,
|
|
103
|
+
* restores it, and commits it as a new head. Cleared by the next
|
|
104
|
+
* successful execute_batch (a fresh edit invalidates the redo chain) and
|
|
105
|
+
* dropped with the session on close/evict.
|
|
106
|
+
*
|
|
107
|
+
* R-2 exactness: entries are immutable pristine copies (the stack
|
|
108
|
+
* deep-copies every pushed map, so later live-package mutations can never
|
|
109
|
+
* corrupt a stored source), each bound to the sha256 fingerprint recorded
|
|
110
|
+
* at push time. A redo must restore EXACTLY the stored pre-revert bytes or
|
|
111
|
+
* fail with E_STATE (see assertRedoSnapshotExact) — never a third mixed
|
|
112
|
+
* state. The stack stays Array-compatible (push/pop/index/length) so the
|
|
113
|
+
* existing session-boundary semantics (cleared on fresh edit, dies with
|
|
114
|
+
* the session) are unchanged.
|
|
115
|
+
*/
|
|
116
|
+
redoStack: SessionRedoStack;
|
|
117
|
+
/**
|
|
118
|
+
* Audit depth (in-memory, per-session): operations + touched node ids
|
|
119
|
+
* accumulated since the last successful save/revert/redo. Consumed at the
|
|
120
|
+
* next history commit to fill the entry's op count + affected node ids,
|
|
121
|
+
* then reset by markSaved. Best-effort and bounded (never fails a batch);
|
|
122
|
+
* it dies with the session and is NOT durable history (see the HOLD note
|
|
123
|
+
* on export_audit_trail: .astra/ itself is deletable local state).
|
|
124
|
+
*/
|
|
125
|
+
pendingAudit: {
|
|
126
|
+
ops: number;
|
|
127
|
+
nodeIds: string[];
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Attached style guide ("aesthetic skin"): set at session creation via
|
|
131
|
+
* new_document/compose_document `styleGuide`, persists for the session,
|
|
132
|
+
* mirrored onto `engine` so batches inherit it when they omit their own.
|
|
133
|
+
* Per-batch `styleGuide` overrides apply to that batch only (restored
|
|
134
|
+
* after). No detach API — open a new session to change guides.
|
|
135
|
+
*/
|
|
136
|
+
styleGuide?: StyleGuideConfig;
|
|
137
|
+
/** Raw guide ref the session guide was resolved from (name or file path). */
|
|
138
|
+
styleGuideRef?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Validator errors the saved state of the session already has, by signature. `undefined` means the
|
|
141
|
+
* open did not finish a check (timed out or skipped); `null` means a check ran but its result
|
|
142
|
+
* cannot be compared (cut short, or an exception).
|
|
143
|
+
*/
|
|
144
|
+
validationBaseline?: ValidationBaseline | null;
|
|
145
|
+
/** The baseline at open, kept when a save replaces `validationBaseline`: a revert may return to a state with these errors. */
|
|
146
|
+
openBaseline?: ValidationBaseline | null;
|
|
147
|
+
/** sha256 of the file bytes read at open, to tell whether the file on disk is still the opened one. */
|
|
148
|
+
openSha256?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Per-commit audit metadata recorded alongside each history commit.
|
|
152
|
+
* Keyed by commit oid in a store-level map (in-memory only: re-opens in a
|
|
153
|
+
* fresh store lose pre-existing entries, and deleting .astra/ wipes the
|
|
154
|
+
* underlying history itself — see the HOLD retention note).
|
|
155
|
+
*/
|
|
156
|
+
export interface CommitAuditMeta {
|
|
157
|
+
author: string;
|
|
158
|
+
/** Wall-clock epoch millis at commit time (git stamps seconds only). */
|
|
159
|
+
timestampMs: number;
|
|
160
|
+
/** Operations folded into this commit (saves: since last save; reverts: steps back; redos: 1). */
|
|
161
|
+
opCount: number;
|
|
162
|
+
/** Node ids touched by this commit (bounded, document order, best-effort). */
|
|
163
|
+
affectedNodeIds: string[];
|
|
164
|
+
}
|
|
165
|
+
/** Cap on affected node ids retained per commit (bounded envelope). */
|
|
166
|
+
export declare const COMMIT_AUDIT_NODE_IDS_MAX = 100;
|
|
167
|
+
/** Cap on commit-meta entries retained per store (bounded ring, oldest dropped). */
|
|
168
|
+
export declare const COMMIT_AUDIT_META_MAX = 2000;
|
|
169
|
+
/** Error code for exact-state violations (redo/restore source diverged). */
|
|
170
|
+
export declare const REDO_EXACTNESS_CODE: "E_STATE";
|
|
171
|
+
/** sha256 hex over one package-part buffer (redo integrity binding). */
|
|
172
|
+
export declare function hashRedoBytes(buf: Buffer): string;
|
|
173
|
+
/** Integrity fingerprint of one full package snapshot (stable key order). */
|
|
174
|
+
export interface RedoSnapshotIntegrity {
|
|
175
|
+
/** sha256 over word/document.xml bytes ('' when the part is absent). */
|
|
176
|
+
docXmlHash: string;
|
|
177
|
+
/** sha256 over every entry (name + bytes, sorted by name). */
|
|
178
|
+
packageHash: string;
|
|
179
|
+
/** Sorted entry names captured in the snapshot. */
|
|
180
|
+
entryNames: string[];
|
|
181
|
+
/** Total snapshot bytes across all entries. */
|
|
182
|
+
byteSize: number;
|
|
183
|
+
}
|
|
184
|
+
/** Deep-copies a package snapshot (new map, copied buffers). */
|
|
185
|
+
export declare function clonePackageSnapshot(snap: Map<string, Buffer>): Map<string, Buffer>;
|
|
186
|
+
/** Fingerprints a package snapshot without mutating it. */
|
|
187
|
+
export declare function fingerprintPackageSnapshot(snap: Map<string, Buffer>): RedoSnapshotIntegrity;
|
|
188
|
+
/**
|
|
189
|
+
* R-2 session-bound redo stack.
|
|
190
|
+
*
|
|
191
|
+
* Array-compatible (push/pop/index/length behave like the plain array the
|
|
192
|
+
* callers already use, including `length = 0` truncation to invalidate the
|
|
193
|
+
* chain), with two hardening guarantees on top:
|
|
194
|
+
* - push() stores an immutable pristine copy (later live-package mutations
|
|
195
|
+
* can never corrupt the redo source by aliasing);
|
|
196
|
+
* - every stored copy is bound to the fingerprint recorded at push time,
|
|
197
|
+
* retrievable via integrityOf() for exactness proofs.
|
|
198
|
+
*
|
|
199
|
+
* Integrity metadata lives in a WeakMap keyed by the stored copy, so
|
|
200
|
+
* `length = 0` truncation or close/evict simply orphans the metadata (no
|
|
201
|
+
* parallel-array bookkeeping to go stale) and entries are never shared
|
|
202
|
+
* back out for mutation.
|
|
203
|
+
*/
|
|
204
|
+
export declare class SessionRedoStack extends Array<Map<string, Buffer>> {
|
|
205
|
+
private readonly integrity;
|
|
206
|
+
private readonly maxDepth;
|
|
207
|
+
private readonly maxBytes;
|
|
208
|
+
constructor(opts?: {
|
|
209
|
+
maxDepth?: number;
|
|
210
|
+
maxBytes?: number;
|
|
211
|
+
});
|
|
212
|
+
getMaxDepth(): number;
|
|
213
|
+
getMaxBytes(): number;
|
|
214
|
+
/** Sum of stored snapshot bytes (recomputed so pop/length-truncation stay exact). */
|
|
215
|
+
currentBytes(): number;
|
|
216
|
+
push(...items: Array<Map<string, Buffer>>): number;
|
|
217
|
+
/** Newest entry without consuming (undefined when the stack is empty). */
|
|
218
|
+
peek(): Map<string, Buffer> | undefined;
|
|
219
|
+
/**
|
|
220
|
+
* Push-time fingerprint of a stored entry (undefined for foreign maps
|
|
221
|
+
* that bypassed push, e.g. entries built by hand in tests).
|
|
222
|
+
*/
|
|
223
|
+
integrityOf(entry: Map<string, Buffer>): RedoSnapshotIntegrity | undefined;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* R-2 exact-or-error check: asserts that a restored live snapshot is
|
|
227
|
+
* byte-identical to the redo source it must equal (pre-revert on success,
|
|
228
|
+
* post-revert when the restore was rolled back). Throws E_STATE with a
|
|
229
|
+
* remedy naming the divergent parts when exact restore is impossible, so
|
|
230
|
+
* callers fail loudly instead of serving a third mixed state.
|
|
231
|
+
*/
|
|
232
|
+
export declare function assertRedoSnapshotExact(expected: Map<string, Buffer>, actual: Map<string, Buffer>, context: string): void;
|
|
233
|
+
export interface McpSessionStats {
|
|
234
|
+
sessions: number;
|
|
235
|
+
totalBytes: number;
|
|
236
|
+
maxDocs: number;
|
|
237
|
+
maxBytes: number;
|
|
238
|
+
idleMs: number;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* M3: workspace jail for MCP document paths.
|
|
242
|
+
* Mirrors the narrow sandbox (cwd/documents, tmpdir, app Drafts, extra roots)
|
|
243
|
+
* without importing the template-palette owner's modules (zero overlap).
|
|
244
|
+
* Every candidate is canonicalized (symlinks/junctions resolved, with
|
|
245
|
+
* nearest-existing-ancestor fallback for not-yet-created save targets) and
|
|
246
|
+
* containment is checked on the CANONICAL path.
|
|
247
|
+
*
|
|
248
|
+
* H2/D3: `ASTRA_ALLOWED_DIR` is NOT honored here (dropped: roots come only
|
|
249
|
+
* from explicit `--allowed-dir` flags, which the server entrypoint folds into
|
|
250
|
+
* `ASTRA_EXTRA_DOC_ROOTS`, plus the fixed base roots below). Any
|
|
251
|
+
* `ASTRA_ALLOWED_DIR` value in the environment is ignored.
|
|
252
|
+
* H2: delimiter-split `ASTRA_EXTRA_DOC_ROOTS` entries are existence-checked
|
|
253
|
+
* (must resolve to an existing directory) before joining the jail — phantom
|
|
254
|
+
* entries never widen anything. Primitives (`isMcpPathInsideDir`,
|
|
255
|
+
* `canonicalizeForMcp`) are the Q0a shared module (`@attentionform/astra-engine`
|
|
256
|
+
* sandbox/paths), so MCP/engine verdicts can never disagree on semantics.
|
|
257
|
+
*/
|
|
258
|
+
export declare function getMcpAllowedRoots(): string[];
|
|
259
|
+
export { canonicalizeForMcp };
|
|
260
|
+
/**
|
|
261
|
+
* M3: jailed document-path validator.
|
|
262
|
+
* Returns the resolved absolute path. Throws E_SANDBOX (with remedy) on:
|
|
263
|
+
* non-string/empty, null byte, line break, `..` traversal, hidden segments,
|
|
264
|
+
* or canonical location outside the workspace roots. Throws E_VALIDATION on
|
|
265
|
+
* non-.docx extension. Symlinks/junctions are resolved BEFORE containment.
|
|
266
|
+
*/
|
|
267
|
+
export declare function validateDocPath(rawPath: unknown): string;
|
|
268
|
+
/**
|
|
269
|
+
* M3: save-target validator (same jail as open).
|
|
270
|
+
* Save targets that don't exist yet are canonicalized via their nearest
|
|
271
|
+
* existing ancestor, so a symlinked parent dir pointing outside still fails.
|
|
272
|
+
*/
|
|
273
|
+
export declare function validateSaveTarget(rawPath: unknown): string;
|
|
274
|
+
/**
|
|
275
|
+
* L4: openBuffer label validator (jails the `.astra` git sidecar).
|
|
276
|
+
* Absolute labels go through the full `validateDocPath` jail (E_SANDBOX on
|
|
277
|
+
* escape, E_VALIDATION on non-.docx) so `/etc/x.docx` can never become a
|
|
278
|
+
* sidecar write at `/etc/.astra/x.docx`. Relative memory labels are
|
|
279
|
+
* sanitized to a bare `.docx` basename (`memory.docx` fallback) and never
|
|
280
|
+
* produce an absolute sidecar path (git init skips non-absolute handles).
|
|
281
|
+
*/
|
|
282
|
+
export declare function validateOpenBufferLabel(rawLabel: unknown): string;
|
|
283
|
+
/**
|
|
284
|
+
* Collects the versioned package extras (media/rels/styles/headers) for git.
|
|
285
|
+
* document.xml is committed separately; everything matching
|
|
286
|
+
* isTrackedPackageFile is snapshotted alongside it.
|
|
287
|
+
*/
|
|
288
|
+
export declare function collectTrackedExtrasForGit(parser: DocxParser): Record<string, Buffer>;
|
|
289
|
+
/**
|
|
290
|
+
* Ensures the sidecar git repo exists for `docPath`, creating it with the
|
|
291
|
+
* current package state as the initial baseline when absent.
|
|
292
|
+
* Returns true when the repo already existed, false when freshly created.
|
|
293
|
+
* Best-effort at open (callers swallow failures so open semantics never
|
|
294
|
+
* change); hard requirement before commitChange at save/revert/redo.
|
|
295
|
+
*/
|
|
296
|
+
export declare function ensureGitRepo(docPath: string, xml: string, extras: Record<string, Buffer>): Promise<boolean>;
|
|
297
|
+
/** Outcome of McpSessionStore.closeDocument (dirty state read under the delete lock). */
|
|
298
|
+
export interface CloseDocumentOutcome {
|
|
299
|
+
closed: boolean;
|
|
300
|
+
/** True when the dropped session held unsaved edits (discarded with the close). */
|
|
301
|
+
wasDirty: boolean;
|
|
302
|
+
/** Pending revisions discarded with the close (0 when clean). */
|
|
303
|
+
discardedRevisions: number;
|
|
304
|
+
}
|
|
305
|
+
export declare class McpSessionStore {
|
|
306
|
+
private readonly sessions;
|
|
307
|
+
private readonly maxDocs;
|
|
308
|
+
private readonly maxBytes;
|
|
309
|
+
private readonly idleMs;
|
|
310
|
+
private readonly allowClientSessionIds;
|
|
311
|
+
private readonly redoMaxDepth;
|
|
312
|
+
private readonly redoMaxBytes;
|
|
313
|
+
/**
|
|
314
|
+
* Phase C-4: ring of sessionIds dropped by eviction (LRU/byte-cap/idle),
|
|
315
|
+
* so later unknown-session failures can report a capacity-specific
|
|
316
|
+
* E_SESSION remedy instead of a generic unknown-id error. Explicit
|
|
317
|
+
* close_document deletes are NOT recorded here. Bounded (last 200).
|
|
318
|
+
*/
|
|
319
|
+
private readonly evictionHistory;
|
|
320
|
+
/** Ids evicted by the most recent open (drained by handleOpenDocument for `evicted[]`). */
|
|
321
|
+
private lastOpenEvicted;
|
|
322
|
+
/**
|
|
323
|
+
* Store-level commit audit metadata (oid -> meta). In-memory only: fresh
|
|
324
|
+
* stores start empty and .astra/ deletion wipes the history it annotates.
|
|
325
|
+
*/
|
|
326
|
+
private readonly commitMeta;
|
|
327
|
+
constructor(opts?: McpSessionStoreOptions);
|
|
328
|
+
getAllowClientSessionIds(): boolean;
|
|
329
|
+
getMaxDocs(): number;
|
|
330
|
+
getMaxBytes(): number;
|
|
331
|
+
getIdleMs(): number;
|
|
332
|
+
getRedoMaxDepth(): number;
|
|
333
|
+
getRedoMaxBytes(): number;
|
|
334
|
+
/** Fresh redo stack bound to this store's quotas. */
|
|
335
|
+
createRedoStack(): SessionRedoStack;
|
|
336
|
+
size(): number;
|
|
337
|
+
totalBytes(): number;
|
|
338
|
+
getStats(): McpSessionStats;
|
|
339
|
+
listSessionIds(): string[];
|
|
340
|
+
/** Evicts sessions idle longer than idleMs. Locked/dirty sessions are refused (never dropped). Returns evicted ids. */
|
|
341
|
+
evictExpired(now?: number): string[];
|
|
342
|
+
/** Phase C-4: record evicted ids (bounded ring) for capacity-aware remedies. */
|
|
343
|
+
private recordEvicted;
|
|
344
|
+
/** Phase C-4: true when `id` was dropped by eviction (not explicit close). */
|
|
345
|
+
wasEvicted(sessionId: string): boolean;
|
|
346
|
+
/**
|
|
347
|
+
* Phase C-4: ids evicted by the most recent openDocument/openBuffer call.
|
|
348
|
+
* Drained (cleared) on read so each open reports only its own evictions.
|
|
349
|
+
*/
|
|
350
|
+
drainLastOpenEvicted(): string[];
|
|
351
|
+
/** Phase C-1b: true when the in-memory session holds unsaved edits. */
|
|
352
|
+
isDirty(handle: DocHandle): boolean;
|
|
353
|
+
/** Phase C-1b: re-syncs the saved marker after a successful save_document. */
|
|
354
|
+
markSaved(handle: DocHandle): void;
|
|
355
|
+
/**
|
|
356
|
+
* Audit depth: appends one batch's footprint to the since-save accumulator.
|
|
357
|
+
* Dedupes ids, preserves first-seen order, caps the list (best-effort:
|
|
358
|
+
* never throws, so a batch can never fail on audit bookkeeping).
|
|
359
|
+
*/
|
|
360
|
+
accumulatePendingAudit(handle: DocHandle, appliedCount: number, nodeIds: readonly string[]): void;
|
|
361
|
+
/** Clears the since-save audit accumulator (called by markSaved). */
|
|
362
|
+
resetPendingAudit(handle: DocHandle): void;
|
|
363
|
+
/** Records per-commit audit metadata (bounded ring; oldest entries dropped). */
|
|
364
|
+
recordCommitMeta(oid: string, meta: CommitAuditMeta): void;
|
|
365
|
+
/** Reads per-commit audit metadata (undefined for commits made before tracking). */
|
|
366
|
+
getCommitMeta(oid: string): CommitAuditMeta | undefined;
|
|
367
|
+
/** LRU + byte-cap eviction (oldest insertion first). Never evicts `protectId`, locked, or dirty sessions. Returns evicted ids. */
|
|
368
|
+
private evictToFit;
|
|
369
|
+
private touch;
|
|
370
|
+
private newSessionId;
|
|
371
|
+
/** Mints an id no live session holds (retries on the near-impossible clash). */
|
|
372
|
+
private mintUniqueSessionId;
|
|
373
|
+
/**
|
|
374
|
+
* Resolves the session id for an open: omitted/blank mints a fresh
|
|
375
|
+
* `sess_<uuid>`; a supplied id is rejected with E_VALIDATION unless the
|
|
376
|
+
* deterministic-id hatch is on, and an id already held by a live session
|
|
377
|
+
* fails with E_CONFLICT — the live entry is never replaced.
|
|
378
|
+
*/
|
|
379
|
+
private resolveOpenSessionId;
|
|
380
|
+
/**
|
|
381
|
+
* Opens a .docx file into a new (or reused) session.
|
|
382
|
+
* Revision starts at 1. LRU/byte/idle eviction runs before insert.
|
|
383
|
+
* Macro/active content refuses by default (E_VALIDATION naming the parts);
|
|
384
|
+
* pass {allowMacros:true} to preserve or {stripMacros:true} to strip.
|
|
385
|
+
*/
|
|
386
|
+
openDocument(filePath: string, sessionId?: string, macroOpts?: DocxParserMacroOptions): Promise<DocHandle>;
|
|
387
|
+
/**
|
|
388
|
+
* Opens a document from an in-memory buffer (tests / headless flows).
|
|
389
|
+
* `labelPath` is the display path recorded on the handle. L4: absolute
|
|
390
|
+
* labels are jailed through validateDocPath (same jail as open) so a
|
|
391
|
+
* future production caller cannot turn the `.astra` git sidecar into a
|
|
392
|
+
* write primitive outside the jail (e.g. label `/etc/x.docx` → sidecar
|
|
393
|
+
* `/etc/.astra/x.docx`). Relative memory labels (e.g. `memory.docx`) are
|
|
394
|
+
* sanitized to a bare basename and never touch git (init skips non-absolute
|
|
395
|
+
* paths). Macro/active content refuses by default; pass {allowMacros:true}
|
|
396
|
+
* or {stripMacros:true}.
|
|
397
|
+
*/
|
|
398
|
+
openBuffer(buf: Buffer, labelPath?: string, sessionId?: string, macroOpts?: DocxParserMacroOptions): Promise<DocHandle>;
|
|
399
|
+
/** Returns the live handle (refreshes LRU). Throws E_SESSION with remedy when missing/expired. */
|
|
400
|
+
get(sessionId: string): DocHandle;
|
|
401
|
+
has(sessionId: string): boolean;
|
|
402
|
+
/**
|
|
403
|
+
* Runs `fn` under the handle's own mutex (per-session, never global).
|
|
404
|
+
* Serializes concurrent execute_batch/save races on the same session while
|
|
405
|
+
* leaving other sessions fully parallel.
|
|
406
|
+
* H2: re-validates liveness AFTER acquiring the lock — a concurrent
|
|
407
|
+
* closeDocument that won the race deletes first, and this call fails with
|
|
408
|
+
* E_CONFLICT (not use-after-close).
|
|
409
|
+
*/
|
|
410
|
+
withSession<T>(sessionId: string, fn: (handle: DocHandle) => Promise<T>): Promise<T>;
|
|
411
|
+
/** Bumps the per-session revision after a successful mutation. */
|
|
412
|
+
bumpRevision(handle: DocHandle): number;
|
|
413
|
+
/** Best-effort byte-size refresh after mutations (drives the 200MB cap). */
|
|
414
|
+
refreshByteSize(handle: DocHandle): number;
|
|
415
|
+
/**
|
|
416
|
+
* H2: lock-aware close. Acquires the SAME per-session SessionMutex instead
|
|
417
|
+
* of deleting without a lock (which raced withSession's critical section).
|
|
418
|
+
* - Unknown id -> { closed: false } (already closed/expired; adapter maps to E_SESSION).
|
|
419
|
+
* - Session busy (batch/save/inspect holding the lock) -> throws E_CONFLICT
|
|
420
|
+
* with remedy (caller retries after the in-flight op finishes).
|
|
421
|
+
* - Otherwise the delete itself runs under the mutex, so a withSession that
|
|
422
|
+
* queued between the busy-check and the delete either sees the live handle
|
|
423
|
+
* (if close lost) or fails its post-lock liveness check (if close won).
|
|
424
|
+
* The outcome names the dropped dirty state (wasDirty + discardedRevisions,
|
|
425
|
+
* read under the same lock as the delete) so callers never silently lose
|
|
426
|
+
* unsaved edits: a dirty close warns instead of vanishing quietly.
|
|
427
|
+
*/
|
|
428
|
+
closeDocument(sessionId: string): Promise<CloseDocumentOutcome>;
|
|
429
|
+
clear(): void;
|
|
430
|
+
}
|
|
431
|
+
/** Factory (no singleton): each server/test constructs its own store. */
|
|
432
|
+
export declare function createMcpSessionStore(opts?: McpSessionStoreOptions): McpSessionStore;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type EngineErrorCode, type ResolvedStyleGuide, type StyleGuideConfig, type StyleGuideSummary } from '@attentionform/astra-engine';
|
|
3
|
+
import { type DocHandle, type McpSessionStore } from '../lib/session-store';
|
|
4
|
+
import { type BatchOpCreatedIds } from '../lib/mcp-tool-adapter';
|
|
5
|
+
/** Upper bound on the Markdown source per call (chars). Larger sources should be split by the caller. */
|
|
6
|
+
export declare const COMPOSE_MARKDOWN_MAX = 500000;
|
|
7
|
+
export interface McpGuideJail {
|
|
8
|
+
/**
|
|
9
|
+
* Explicit jail directory (already validated against the MCP roots, e.g.
|
|
10
|
+
* the batch/compose `allowedDir`). Wins over the session anchor.
|
|
11
|
+
*/
|
|
12
|
+
allowedDir?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Session filesystem anchor (absolute open-document path): file guide
|
|
15
|
+
* refs resolve under its directory (same trust as batch image reads).
|
|
16
|
+
*/
|
|
17
|
+
sessionFilePath?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolves a `styleGuide` ref under the MCP jail. Named guides resolve to
|
|
21
|
+
* bundled files (unknown names fail closed E_VALIDATION naming the
|
|
22
|
+
* available set); file refs resolve under the explicit `allowedDir` else
|
|
23
|
+
* the session anchor directory; remote URLs fail closed. A file-backed ref
|
|
24
|
+
* with no anchor (in-memory session, no allowedDir) fails E_SANDBOX —
|
|
25
|
+
* same rule as batch image `filePath` (never silently cwd-anchored).
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveMcpStyleGuide(ref: string, jail?: McpGuideJail): ResolvedStyleGuide;
|
|
28
|
+
/**
|
|
29
|
+
* Attaches a resolved guide to a session (persists for the session,
|
|
30
|
+
* mirrored onto the session engine so later batches inherit it).
|
|
31
|
+
*/
|
|
32
|
+
export declare function attachSessionStyleGuide(handle: DocHandle, ref: string, resolved: ResolvedStyleGuide): StyleGuideConfig;
|
|
33
|
+
export declare const ComposeDocumentSchema: z.ZodObject<{
|
|
34
|
+
sessionId: z.ZodString;
|
|
35
|
+
markdown: z.ZodString;
|
|
36
|
+
allowedDir: z.ZodOptional<z.ZodString>;
|
|
37
|
+
template: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
report: "report";
|
|
39
|
+
memo: "memo";
|
|
40
|
+
blank: "blank";
|
|
41
|
+
}>>;
|
|
42
|
+
styleGuide: z.ZodOptional<z.ZodString>;
|
|
43
|
+
dollarMath: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
}, z.core.$strict>;
|
|
45
|
+
export type ComposeDocumentInput = z.infer<typeof ComposeDocumentSchema>;
|
|
46
|
+
export declare const COMPOSE_DOCUMENT_DESCRIPTION: string;
|
|
47
|
+
type ComposeDocumentOk = {
|
|
48
|
+
ok: true;
|
|
49
|
+
sessionId: string;
|
|
50
|
+
/** Operations the source compiled to (before chunking). */
|
|
51
|
+
opCount: number;
|
|
52
|
+
appliedCount: number;
|
|
53
|
+
revision: number;
|
|
54
|
+
/** True when the in-memory session differs from the last saved file (always true after a compose). */
|
|
55
|
+
dirty: boolean;
|
|
56
|
+
/** New node ids created by this compose, in post-compose document order. */
|
|
57
|
+
createdNodeIds: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Created paragraph ids (`md_0003`) mapped to the w14:paraId the saved file carries (what
|
|
60
|
+
* a reopened session reports). Absent when nothing mapped; tables have no paraId and are
|
|
61
|
+
* renumbered by position on reopen.
|
|
62
|
+
*/
|
|
63
|
+
persistentIds?: Record<string, string>;
|
|
64
|
+
/** Per-op attribution (same rule as execute_batch: exact id claims, remainder best-effort). */
|
|
65
|
+
results: BatchOpCreatedIds[];
|
|
66
|
+
/** Read-only post-compose OOXML verdict (proof the composed package validates; save re-gates before writing). */
|
|
67
|
+
validation: {
|
|
68
|
+
valid: boolean;
|
|
69
|
+
errorCount: number;
|
|
70
|
+
errors: Array<{
|
|
71
|
+
description: string;
|
|
72
|
+
xPath?: string;
|
|
73
|
+
}>;
|
|
74
|
+
};
|
|
75
|
+
skippedIds: string[];
|
|
76
|
+
warnings: string[];
|
|
77
|
+
revisionsPending: number;
|
|
78
|
+
revisionsRemedy?: string;
|
|
79
|
+
possiblyStaleBodyRefs: {
|
|
80
|
+
nodeId: string;
|
|
81
|
+
preview: string;
|
|
82
|
+
}[];
|
|
83
|
+
normalized: string[];
|
|
84
|
+
/** True only when this compose dropped a non-empty redo stack (absent otherwise). */
|
|
85
|
+
redoCleared?: boolean;
|
|
86
|
+
/** Names the dropped redo depth (present only when redoCleared). */
|
|
87
|
+
redoClearedWarning?: string;
|
|
88
|
+
/** Compact summary of the guide this compose attached (absent when none attached). */
|
|
89
|
+
styleGuide?: StyleGuideSummary;
|
|
90
|
+
/** Parser diagnostics of the attached guide (present only when this compose attached one). */
|
|
91
|
+
styleGuideWarnings?: string[];
|
|
92
|
+
};
|
|
93
|
+
type ComposeDocumentErr = {
|
|
94
|
+
ok: false;
|
|
95
|
+
code: EngineErrorCode;
|
|
96
|
+
message: string;
|
|
97
|
+
remedy: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Q0b-gap: pure mapper for compose-stage failures (exported for the E-code
|
|
101
|
+
* contract test). Every engine code survives verbatim via the shared
|
|
102
|
+
* getEngineErrorCode table; E_VALIDATION stays the fallback for uncoded
|
|
103
|
+
* failures. Previously E_LIMIT/E_SECURITY/E_DEPENDENCY/E_AUTH collapsed to
|
|
104
|
+
* E_VALIDATION via the local KNOWN_COMPOSE_CODES allowlist.
|
|
105
|
+
*/
|
|
106
|
+
export declare function composeErrorFrom(err: unknown, fallbackRemedy: string): ComposeDocumentErr;
|
|
107
|
+
/**
|
|
108
|
+
* Jail check for the asset directory: the resolved canonical location must
|
|
109
|
+
* sit inside one of the workspace roots. Returns the canonical absolute path.
|
|
110
|
+
* Throws E_SANDBOX (with remedy) when the directory escapes the roots.
|
|
111
|
+
*/
|
|
112
|
+
export declare function assertPathAllowed(allowedDir: string, roots: string[]): string;
|
|
113
|
+
/**
|
|
114
|
+
* Compile Markdown and apply it to an open session through the engine compose
|
|
115
|
+
* path. The compile is pure (runs before the session lock, so input errors
|
|
116
|
+
* write nothing and touch no session); execution delegates to
|
|
117
|
+
* `composeDocument` on the session parser (chunked at the engine op cap with
|
|
118
|
+
* template-seed check, then inline-span run splitting), so the strict schema
|
|
119
|
+
* gate, quotas, structural checks and per-chunk atomic rollback all apply
|
|
120
|
+
* exactly as they do for execute_batch, plus spans. Never throws — every
|
|
121
|
+
* failure maps to {ok:false,code,remedy}.
|
|
122
|
+
*/
|
|
123
|
+
export declare function handleComposeDocument(store: McpSessionStore, rawArgs: unknown): Promise<ComposeDocumentOk | ComposeDocumentErr>;
|
|
124
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { McpSessionStore } from '../lib/session-store';
|
|
3
|
+
import { type PreflightIssue } from '@attentionform/astra-engine';
|
|
4
|
+
export declare const ExportDocumentSchema: z.ZodObject<{
|
|
5
|
+
sessionId: z.ZodString;
|
|
6
|
+
format: z.ZodEnum<{
|
|
7
|
+
markdown: "markdown";
|
|
8
|
+
html: "html";
|
|
9
|
+
md: "md";
|
|
10
|
+
pdf: "pdf";
|
|
11
|
+
}>;
|
|
12
|
+
outPath: z.ZodOptional<z.ZodString>;
|
|
13
|
+
imageMode: z.ZodOptional<z.ZodEnum<{
|
|
14
|
+
file: "file";
|
|
15
|
+
embed: "embed";
|
|
16
|
+
}>>;
|
|
17
|
+
assetPrefix: z.ZodOptional<z.ZodString>;
|
|
18
|
+
tagged_pdf: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
tagged: z.ZodOptional<z.ZodBoolean>;
|
|
20
|
+
strict: z.ZodOptional<z.ZodBoolean>;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export type ExportDocumentArgs = z.infer<typeof ExportDocumentSchema>;
|
|
23
|
+
export declare const EXPORT_DOCUMENT_DESCRIPTION: string;
|
|
24
|
+
/**
|
|
25
|
+
* Q0b: pure mapper for export-stage failures (exported for the E-code
|
|
26
|
+
* contract test). Every engine code survives verbatim via
|
|
27
|
+
* getEngineErrorCode; E_IO stays the fallback for uncoded failures.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mapExportError(err: unknown): {
|
|
30
|
+
code: string;
|
|
31
|
+
message: string;
|
|
32
|
+
remedy: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function validateExportTarget(rawPath: unknown, expectedFormat: 'html' | 'markdown' | 'md' | 'pdf'): string;
|
|
35
|
+
export declare function validateAssetTarget(rawPath: unknown): string;
|
|
36
|
+
export declare function handleExportDocument(store: McpSessionStore, rawArgs: unknown): Promise<{
|
|
37
|
+
ok: boolean;
|
|
38
|
+
format?: string;
|
|
39
|
+
path?: string;
|
|
40
|
+
bytes?: number;
|
|
41
|
+
content?: string;
|
|
42
|
+
dirty?: boolean;
|
|
43
|
+
revision?: number;
|
|
44
|
+
imageMode?: 'file' | 'embed';
|
|
45
|
+
assetPrefix?: string;
|
|
46
|
+
assets?: string[];
|
|
47
|
+
assetCount?: number;
|
|
48
|
+
warnings?: PreflightIssue[];
|
|
49
|
+
blockers?: PreflightIssue[];
|
|
50
|
+
message?: string;
|
|
51
|
+
code?: string;
|
|
52
|
+
remedy?: string;
|
|
53
|
+
}>;
|