@agent-native/core 0.175.3 → 0.175.4-nightly-20260826190205
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/corpus/README.md +1 -1
- package/corpus/templates/clips/server/plugins/agent-chat.ts +3 -0
- package/corpus/templates/content/actions/_linked-local-document-edit.ts +167 -0
- package/corpus/templates/content/actions/edit-document.ts +181 -61
- package/corpus/templates/content/actions/import-content-source.ts +33 -1
- package/corpus/templates/content/actions/update-document.ts +2 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +68 -0
- package/corpus/templates/content/app/components/editor/LinkedLocalDocumentAgentBridge.tsx +245 -0
- package/corpus/templates/content/app/lib/local-content-source-files.ts +96 -9
- package/corpus/templates/content/shared/api.ts +2 -0
- package/corpus/templates/content/shared/content-source.ts +14 -0
- package/corpus/templates/content/shared/document-text-edits.ts +39 -0
- package/corpus/templates/content/shared/nfm.ts +347 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
- package/dist/agent/engine/ai-sdk-engine.js +14 -4
- package/dist/agent/engine/anthropic-engine.js +6 -4
- package/dist/agent/engine/builder-engine.js +11 -4
- package/dist/agent/engine/tool-name.d.ts +9 -0
- package/dist/agent/engine/tool-name.js +56 -0
- package/dist/agent/engine/translate-ai-sdk.d.ts +5 -3
- package/dist/agent/engine/translate-ai-sdk.js +16 -13
- package/dist/agent/engine/translate-anthropic.d.ts +8 -6
- package/dist/agent/engine/translate-anthropic.js +21 -22
- package/dist/agent/production-agent.js +23 -5
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/fetch-tool.js +73 -5
- package/dist/extensions/proxy-security.d.ts +5 -0
- package/dist/extensions/proxy-security.js +21 -13
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/server/agent-chat/context-tools.js +5 -0
- package/dist/server/credential-provider.js +47 -10
- package/dist/server/realtime-token.d.ts +1 -1
- package/docs/content/webmcp.mdx +5 -5
- package/package.json +1 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { getBrowserTabId } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { startAgentNativeBrowserSessionBridge } from "@agent-native/core/client/host";
|
|
3
|
+
import type { AgentNativeClientAction } from "@agent-native/core/client/host";
|
|
4
|
+
import type { Document } from "@shared/api";
|
|
5
|
+
import {
|
|
6
|
+
applyDocumentTextEdits,
|
|
7
|
+
type DocumentTextEdit,
|
|
8
|
+
} from "@shared/document-text-edits";
|
|
9
|
+
import { useEffect, useRef } from "react";
|
|
10
|
+
|
|
11
|
+
import type { DesktopContentFileRevision } from "@/lib/desktop-content-files";
|
|
12
|
+
import {
|
|
13
|
+
readDocumentFromLinkedLocalSource,
|
|
14
|
+
writeDocumentToLinkedLocalSource,
|
|
15
|
+
} from "@/lib/local-content-source-files";
|
|
16
|
+
|
|
17
|
+
type EditArgs = {
|
|
18
|
+
documentId: string;
|
|
19
|
+
expectedContent: string;
|
|
20
|
+
expectedTitle: string;
|
|
21
|
+
expectedDescription: string;
|
|
22
|
+
expectedMetadata: string;
|
|
23
|
+
edits: DocumentTextEdit[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const actionName = (documentId: string) =>
|
|
27
|
+
`content-edit-linked-document:${documentId}`;
|
|
28
|
+
|
|
29
|
+
function managedDocumentMetadata(document: Document) {
|
|
30
|
+
return {
|
|
31
|
+
parentId: document.parentId,
|
|
32
|
+
icon: document.icon,
|
|
33
|
+
position: document.position,
|
|
34
|
+
isFavorite: document.isFavorite,
|
|
35
|
+
hideFromSearch: document.hideFromSearch,
|
|
36
|
+
visibility: document.visibility,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function LinkedLocalDocumentAgentBridge({
|
|
41
|
+
document,
|
|
42
|
+
getEditorSnapshot,
|
|
43
|
+
onPersisted,
|
|
44
|
+
}: {
|
|
45
|
+
document: Document;
|
|
46
|
+
getEditorSnapshot(): { title: string; content: string };
|
|
47
|
+
onPersisted(document: Document, revision?: DesktopContentFileRevision): void;
|
|
48
|
+
}) {
|
|
49
|
+
const documentRef = useRef(document);
|
|
50
|
+
documentRef.current = document;
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
let active = true;
|
|
54
|
+
const action: AgentNativeClientAction<EditArgs> = {
|
|
55
|
+
name: actionName(document.id),
|
|
56
|
+
description: actionName(document.id),
|
|
57
|
+
schema: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
documentId: { type: "string" },
|
|
61
|
+
expectedContent: { type: "string" },
|
|
62
|
+
expectedTitle: { type: "string" },
|
|
63
|
+
expectedDescription: { type: "string" },
|
|
64
|
+
expectedMetadata: { type: "string" },
|
|
65
|
+
edits: { type: "array" },
|
|
66
|
+
},
|
|
67
|
+
required: [
|
|
68
|
+
"documentId",
|
|
69
|
+
"expectedContent",
|
|
70
|
+
"expectedTitle",
|
|
71
|
+
"expectedDescription",
|
|
72
|
+
"expectedMetadata",
|
|
73
|
+
"edits",
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
async run(args) {
|
|
77
|
+
const current = documentRef.current;
|
|
78
|
+
if (args.documentId !== current.id) {
|
|
79
|
+
return { status: "conflict", error: "The open document changed." };
|
|
80
|
+
}
|
|
81
|
+
const editorMatchesExpected = () => {
|
|
82
|
+
const editor = getEditorSnapshot();
|
|
83
|
+
return (
|
|
84
|
+
editor.content === args.expectedContent &&
|
|
85
|
+
editor.title === args.expectedTitle
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
if (!editorMatchesExpected()) {
|
|
89
|
+
return {
|
|
90
|
+
status: "conflict",
|
|
91
|
+
error:
|
|
92
|
+
"Save the pending editor changes before retrying the agent edit.",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const baseline = await readDocumentFromLinkedLocalSource(current);
|
|
96
|
+
if (!active || documentRef.current.id !== args.documentId) {
|
|
97
|
+
return { status: "conflict", error: "The open document changed." };
|
|
98
|
+
}
|
|
99
|
+
if (!baseline.ok) {
|
|
100
|
+
return {
|
|
101
|
+
status: baseline.unavailable ? "unavailable" : "failed",
|
|
102
|
+
error: baseline.error,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (
|
|
106
|
+
baseline.document.content !== args.expectedContent ||
|
|
107
|
+
baseline.document.title !== args.expectedTitle ||
|
|
108
|
+
(baseline.document.description ?? "") !== args.expectedDescription ||
|
|
109
|
+
JSON.stringify(managedDocumentMetadata(baseline.document)) !==
|
|
110
|
+
args.expectedMetadata
|
|
111
|
+
) {
|
|
112
|
+
return {
|
|
113
|
+
status: "conflict",
|
|
114
|
+
error: "The linked file changed after the agent loaded it.",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const applied = applyDocumentTextEdits(
|
|
118
|
+
baseline.document.content,
|
|
119
|
+
args.edits,
|
|
120
|
+
);
|
|
121
|
+
if (applied.changeCount === 0) {
|
|
122
|
+
return {
|
|
123
|
+
status: "failed",
|
|
124
|
+
error: "None of the requested text was found in the linked file.",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const next = { ...baseline.document, content: applied.content };
|
|
128
|
+
if (!editorMatchesExpected()) {
|
|
129
|
+
return {
|
|
130
|
+
status: "conflict",
|
|
131
|
+
error:
|
|
132
|
+
"The editor changed while the agent edit was being prepared.",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (!active || documentRef.current.id !== args.documentId) {
|
|
136
|
+
return { status: "conflict", error: "The open document changed." };
|
|
137
|
+
}
|
|
138
|
+
const written = await writeDocumentToLinkedLocalSource(
|
|
139
|
+
next,
|
|
140
|
+
current.source,
|
|
141
|
+
{ expectedRevision: baseline.revision },
|
|
142
|
+
);
|
|
143
|
+
if (!written.ok) {
|
|
144
|
+
return {
|
|
145
|
+
status: written.conflict
|
|
146
|
+
? "conflict"
|
|
147
|
+
: written.unavailable
|
|
148
|
+
? "unavailable"
|
|
149
|
+
: "failed",
|
|
150
|
+
error: written.error,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const readBack = await readDocumentFromLinkedLocalSource(next);
|
|
154
|
+
if (!active || documentRef.current.id !== args.documentId) {
|
|
155
|
+
return {
|
|
156
|
+
status: "source-persisted/history-pending",
|
|
157
|
+
content: next.content,
|
|
158
|
+
title: next.title,
|
|
159
|
+
description: next.description ?? "",
|
|
160
|
+
metadata: managedDocumentMetadata(next),
|
|
161
|
+
path: written.path,
|
|
162
|
+
runtime: written.runtime,
|
|
163
|
+
revision: written.revision,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (!readBack.ok) {
|
|
167
|
+
return {
|
|
168
|
+
status: "source-persisted/readback-pending",
|
|
169
|
+
content: next.content,
|
|
170
|
+
title: next.title,
|
|
171
|
+
path: written.path,
|
|
172
|
+
runtime: written.runtime,
|
|
173
|
+
revision: written.revision,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const revisionMatches =
|
|
177
|
+
!written.revision ||
|
|
178
|
+
!readBack.revision ||
|
|
179
|
+
written.revision === readBack.revision;
|
|
180
|
+
const managedMetadataMatches =
|
|
181
|
+
JSON.stringify(managedDocumentMetadata(readBack.document)) ===
|
|
182
|
+
JSON.stringify(managedDocumentMetadata(next));
|
|
183
|
+
if (
|
|
184
|
+
!revisionMatches ||
|
|
185
|
+
readBack.document.content !== applied.content ||
|
|
186
|
+
!managedMetadataMatches ||
|
|
187
|
+
(readBack.document.description ?? "") !== (next.description ?? "") ||
|
|
188
|
+
readBack.document.title !== next.title
|
|
189
|
+
) {
|
|
190
|
+
return {
|
|
191
|
+
status: "source-persisted/history-pending",
|
|
192
|
+
content: readBack.document.content,
|
|
193
|
+
title: readBack.document.title,
|
|
194
|
+
description: readBack.document.description ?? "",
|
|
195
|
+
metadata: managedDocumentMetadata(readBack.document),
|
|
196
|
+
path: readBack.path,
|
|
197
|
+
runtime: readBack.runtime,
|
|
198
|
+
revision: readBack.revision,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (!editorMatchesExpected()) {
|
|
202
|
+
return {
|
|
203
|
+
status: "source-persisted/history-pending",
|
|
204
|
+
content: readBack.document.content,
|
|
205
|
+
title: readBack.document.title,
|
|
206
|
+
description: readBack.document.description ?? "",
|
|
207
|
+
metadata: managedDocumentMetadata(readBack.document),
|
|
208
|
+
path: readBack.path,
|
|
209
|
+
runtime: readBack.runtime,
|
|
210
|
+
revision: readBack.revision,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
onPersisted(readBack.document, readBack.revision);
|
|
214
|
+
return {
|
|
215
|
+
status: "persisted",
|
|
216
|
+
content: readBack.document.content,
|
|
217
|
+
title: readBack.document.title,
|
|
218
|
+
path: readBack.path,
|
|
219
|
+
runtime: readBack.runtime,
|
|
220
|
+
revision: readBack.revision,
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
const bridge = startAgentNativeBrowserSessionBridge({
|
|
225
|
+
session: {
|
|
226
|
+
id: `${getBrowserTabId()}:content-page:${document.id}`,
|
|
227
|
+
label: document.title,
|
|
228
|
+
connectedAt: new Date().toISOString(),
|
|
229
|
+
url: window.location.href,
|
|
230
|
+
},
|
|
231
|
+
label: `Content: ${document.title}`,
|
|
232
|
+
getContext: () => ({
|
|
233
|
+
route: { pathname: window.location.pathname },
|
|
234
|
+
resource: { type: "document", id: document.id },
|
|
235
|
+
}),
|
|
236
|
+
actions: [action],
|
|
237
|
+
});
|
|
238
|
+
return () => {
|
|
239
|
+
active = false;
|
|
240
|
+
bridge.stop();
|
|
241
|
+
};
|
|
242
|
+
}, [document.id, getEditorSnapshot, onPersisted]);
|
|
243
|
+
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
@@ -15,13 +15,18 @@ import type {
|
|
|
15
15
|
type PermissionState = "granted" | "denied" | "prompt";
|
|
16
16
|
type LocalWritable = {
|
|
17
17
|
write(data: string): Promise<void>;
|
|
18
|
+
truncate?(size: number): Promise<void>;
|
|
18
19
|
close(): Promise<void>;
|
|
20
|
+
abort?(reason?: unknown): Promise<void>;
|
|
19
21
|
};
|
|
20
22
|
type LocalFileHandle = {
|
|
21
23
|
kind: "file";
|
|
22
24
|
name: string;
|
|
23
25
|
getFile(): Promise<File>;
|
|
24
|
-
createWritable(
|
|
26
|
+
createWritable(options?: {
|
|
27
|
+
keepExistingData?: boolean;
|
|
28
|
+
mode?: "exclusive" | "siloed";
|
|
29
|
+
}): Promise<LocalWritable>;
|
|
25
30
|
};
|
|
26
31
|
type LocalDirectoryHandle = {
|
|
27
32
|
kind: "directory";
|
|
@@ -309,6 +314,7 @@ async function writeBrowserFile(
|
|
|
309
314
|
root: LocalDirectoryHandle,
|
|
310
315
|
filePath: string,
|
|
311
316
|
content: string,
|
|
317
|
+
expectedRevision: string,
|
|
312
318
|
) {
|
|
313
319
|
const writePath =
|
|
314
320
|
root.name === CONTENT_SOURCE_ROOT &&
|
|
@@ -323,10 +329,55 @@ async function writeBrowserFile(
|
|
|
323
329
|
for (const part of parts) {
|
|
324
330
|
dir = await dir.getDirectoryHandle(part, { create: true });
|
|
325
331
|
}
|
|
326
|
-
const file = await dir.getFileHandle(filename
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
332
|
+
const file = await dir.getFileHandle(filename);
|
|
333
|
+
let writable: LocalWritable;
|
|
334
|
+
try {
|
|
335
|
+
writable = await file.createWritable({
|
|
336
|
+
keepExistingData: true,
|
|
337
|
+
mode: "exclusive",
|
|
338
|
+
});
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (
|
|
341
|
+
error instanceof DOMException &&
|
|
342
|
+
error.name === "NoModificationAllowedError"
|
|
343
|
+
) {
|
|
344
|
+
return { ok: false as const, locked: true as const };
|
|
345
|
+
}
|
|
346
|
+
throw error;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const current = await file.getFile();
|
|
350
|
+
const currentRevision = await browserFileRevision(await current.text());
|
|
351
|
+
if (currentRevision !== expectedRevision) {
|
|
352
|
+
await writable.abort?.(
|
|
353
|
+
new DOMException("The local file changed.", "AbortError"),
|
|
354
|
+
);
|
|
355
|
+
return { ok: false as const, actualRevision: currentRevision };
|
|
356
|
+
}
|
|
357
|
+
if (!writable.truncate) {
|
|
358
|
+
throw new Error("The browser cannot safely replace this local file.");
|
|
359
|
+
}
|
|
360
|
+
await writable.truncate(0);
|
|
361
|
+
await writable.write(content);
|
|
362
|
+
await writable.close();
|
|
363
|
+
} catch (error) {
|
|
364
|
+
await writable.abort?.(error).catch(() => undefined);
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
ok: true as const,
|
|
369
|
+
revision: await browserFileRevision(content),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function browserFileRevision(content: string) {
|
|
374
|
+
const digest = await crypto.subtle.digest(
|
|
375
|
+
"SHA-256",
|
|
376
|
+
new TextEncoder().encode(content),
|
|
377
|
+
);
|
|
378
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) =>
|
|
379
|
+
byte.toString(16).padStart(2, "0"),
|
|
380
|
+
).join("")}`;
|
|
330
381
|
}
|
|
331
382
|
|
|
332
383
|
async function readBrowserFile(root: LocalDirectoryHandle, filePath: string) {
|
|
@@ -345,9 +396,11 @@ async function readBrowserFile(root: LocalDirectoryHandle, filePath: string) {
|
|
|
345
396
|
}
|
|
346
397
|
const handle = await dir.getFileHandle(filename);
|
|
347
398
|
const file = await handle.getFile();
|
|
399
|
+
const content = await file.text();
|
|
348
400
|
return {
|
|
349
|
-
content
|
|
401
|
+
content,
|
|
350
402
|
updatedAt: new Date(file.lastModified).toISOString(),
|
|
403
|
+
revision: await browserFileRevision(content),
|
|
351
404
|
};
|
|
352
405
|
}
|
|
353
406
|
|
|
@@ -373,6 +426,7 @@ export function sourceFileContent(document: Document, existingSource?: string) {
|
|
|
373
426
|
parentId: document.parentId,
|
|
374
427
|
title: document.title,
|
|
375
428
|
content: document.content,
|
|
429
|
+
description: document.description,
|
|
376
430
|
icon: document.icon,
|
|
377
431
|
position: document.position,
|
|
378
432
|
isFavorite: document.isFavorite,
|
|
@@ -419,10 +473,12 @@ function documentFromSourceContent(input: {
|
|
|
419
473
|
parsed.parentId === undefined ? input.base.parentId : parsed.parentId,
|
|
420
474
|
title: parsed.title,
|
|
421
475
|
content: parsed.content,
|
|
476
|
+
description: parsed.description ?? input.base.description,
|
|
422
477
|
icon: parsed.icon === undefined ? null : parsed.icon,
|
|
423
478
|
position: parsed.position ?? input.base.position,
|
|
424
479
|
isFavorite: parsed.isFavorite ?? false,
|
|
425
480
|
hideFromSearch: parsed.hideFromSearch ?? false,
|
|
481
|
+
visibility: parsed.visibility ?? input.base.visibility,
|
|
426
482
|
updatedAt: input.updatedAt,
|
|
427
483
|
source: input.source,
|
|
428
484
|
},
|
|
@@ -538,9 +594,39 @@ export async function writeDocumentToLinkedLocalSource(
|
|
|
538
594
|
browserSource.handle,
|
|
539
595
|
browserSource.path,
|
|
540
596
|
);
|
|
597
|
+
const expectedRevision = options.expectedRevision;
|
|
598
|
+
if (!expectedRevision) {
|
|
599
|
+
return {
|
|
600
|
+
ok: false,
|
|
601
|
+
error: `Local file "${filePath}" has no observed revision.`,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
541
604
|
const content = sourceFileContent(document, existingSource.content);
|
|
542
|
-
await writeBrowserFile(
|
|
543
|
-
|
|
605
|
+
const result = await writeBrowserFile(
|
|
606
|
+
browserSource.handle,
|
|
607
|
+
browserSource.path,
|
|
608
|
+
content,
|
|
609
|
+
expectedRevision,
|
|
610
|
+
);
|
|
611
|
+
if (!result.ok) {
|
|
612
|
+
return {
|
|
613
|
+
ok: false,
|
|
614
|
+
error: result.locked
|
|
615
|
+
? `Local file "${filePath}" is already open for writing.`
|
|
616
|
+
: `Local file "${filePath}" changed after it was read.`,
|
|
617
|
+
conflict: {
|
|
618
|
+
path: filePath,
|
|
619
|
+
expectedRevision,
|
|
620
|
+
actualRevision: result.locked ? undefined : result.actualRevision,
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
ok: true,
|
|
626
|
+
path: filePath,
|
|
627
|
+
runtime: "browser",
|
|
628
|
+
revision: result.revision,
|
|
629
|
+
};
|
|
544
630
|
}
|
|
545
631
|
|
|
546
632
|
/**
|
|
@@ -677,13 +763,14 @@ export async function readDocumentFromLinkedLocalSource(
|
|
|
677
763
|
browserSource.handle,
|
|
678
764
|
browserSource.path,
|
|
679
765
|
);
|
|
680
|
-
|
|
766
|
+
const read = documentFromSourceContent({
|
|
681
767
|
base: document,
|
|
682
768
|
path: filePath,
|
|
683
769
|
source,
|
|
684
770
|
content: file.content,
|
|
685
771
|
updatedAt: file.updatedAt,
|
|
686
772
|
});
|
|
773
|
+
return read.ok ? { ...read, revision: file.revision } : read;
|
|
687
774
|
} catch (error) {
|
|
688
775
|
return {
|
|
689
776
|
ok: false,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlocksFieldIdentity } from "./blocks-field-identity";
|
|
2
|
+
import type { NfmFidelityReport } from "./nfm";
|
|
2
3
|
import type {
|
|
3
4
|
DocumentPropertyOptions,
|
|
4
5
|
DocumentPropertyType,
|
|
@@ -46,6 +47,7 @@ export interface Document {
|
|
|
46
47
|
contextPath?: ContentContextPathEntry[];
|
|
47
48
|
createdAt: string;
|
|
48
49
|
updatedAt: string;
|
|
50
|
+
contentFidelity?: NfmFidelityReport;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
export interface DocumentSourceInfo {
|
|
@@ -37,6 +37,7 @@ export interface ParsedContentSourceFile {
|
|
|
37
37
|
position?: number;
|
|
38
38
|
isFavorite?: boolean;
|
|
39
39
|
hideFromSearch?: boolean;
|
|
40
|
+
visibility?: "private" | "org" | "public";
|
|
40
41
|
errors?: string[];
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -192,6 +193,18 @@ export function parseContentSourceFile(
|
|
|
192
193
|
const rawTitle = typeof metadata.title === "string" ? metadata.title : "";
|
|
193
194
|
const title = rawTitle.trim() || titleFromPath(filePath) || "Untitled";
|
|
194
195
|
const rawPosition = metadata.position;
|
|
196
|
+
let visibility: "private" | "org" | "public" | undefined;
|
|
197
|
+
if (hasOwn(metadata, "visibility")) {
|
|
198
|
+
if (
|
|
199
|
+
metadata.visibility === "private" ||
|
|
200
|
+
metadata.visibility === "org" ||
|
|
201
|
+
metadata.visibility === "public"
|
|
202
|
+
) {
|
|
203
|
+
visibility = metadata.visibility;
|
|
204
|
+
} else {
|
|
205
|
+
errors.push("Invalid visibility frontmatter.");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
195
208
|
|
|
196
209
|
return {
|
|
197
210
|
path: filePath,
|
|
@@ -220,6 +233,7 @@ export function parseContentSourceFile(
|
|
|
220
233
|
typeof metadata.hideFromSearch === "boolean"
|
|
221
234
|
? metadata.hideFromSearch
|
|
222
235
|
: undefined,
|
|
236
|
+
visibility,
|
|
223
237
|
errors: errors.length > 0 ? errors : undefined,
|
|
224
238
|
};
|
|
225
239
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface DocumentTextEdit {
|
|
2
|
+
find: string;
|
|
3
|
+
replace: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function applyDocumentTextEdits(
|
|
7
|
+
source: string,
|
|
8
|
+
edits: DocumentTextEdit[],
|
|
9
|
+
) {
|
|
10
|
+
let content = source;
|
|
11
|
+
const results: string[] = [];
|
|
12
|
+
const appliedEdits: DocumentTextEdit[] = [];
|
|
13
|
+
|
|
14
|
+
for (const edit of edits) {
|
|
15
|
+
const index = content.indexOf(edit.find);
|
|
16
|
+
if (index === -1) {
|
|
17
|
+
results.push(
|
|
18
|
+
`NOT FOUND: "${edit.find.slice(0, 60)}${edit.find.length > 60 ? "..." : ""}"`,
|
|
19
|
+
);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
content =
|
|
23
|
+
content.slice(0, index) +
|
|
24
|
+
edit.replace +
|
|
25
|
+
content.slice(index + edit.find.length);
|
|
26
|
+
appliedEdits.push(edit);
|
|
27
|
+
const action = edit.replace === "" ? "deleted" : "replaced";
|
|
28
|
+
results.push(
|
|
29
|
+
`${action}: "${edit.find.slice(0, 40)}${edit.find.length > 40 ? "..." : ""}"`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
content,
|
|
35
|
+
results,
|
|
36
|
+
appliedEdits,
|
|
37
|
+
changeCount: appliedEdits.length,
|
|
38
|
+
};
|
|
39
|
+
}
|