mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
refactor(context): model Obsidian excerpts as references
This commit is contained in:
parent
4581ee4c5f
commit
9b2585b2bd
11 changed files with 177 additions and 59 deletions
|
|
@ -14,6 +14,8 @@ The panel may acquire prompt context through an explicit user action. `/web` fet
|
|||
|
||||
Panel-acquired context remains reference material rather than current user authorization. Send its bounded payload as untrusted app-server context, while persisting only a small content-free descriptor needed to reconstruct panel display and archive metadata. Apply physical byte and part limits at the app-server boundary; keep source-specific selection and truncation policy with the context producer.
|
||||
|
||||
Vault references must remain model-visible in that explicit context. App-server `mention` items with ordinary Vault paths are retained as panel display metadata, but Codex does not expose those arbitrary paths to the model as file context.
|
||||
|
||||
Panel settings should store only panel-specific preferences. Do not mirror Codex configuration in Obsidian settings just to display or inspect it.
|
||||
|
||||
## Sources of Truth
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@ export function appServerTurnInputFromCodexInput(input: readonly CodexInputItem[
|
|||
].join("\n"),
|
||||
};
|
||||
});
|
||||
manifest.push(manifestEntry(item, id, partCount, sourceBytes, split.includedBytes));
|
||||
if (item.attachment || split.includedBytes < sourceBytes) {
|
||||
manifest.push(manifestEntry(item, id, partCount, sourceBytes, split.includedBytes));
|
||||
}
|
||||
}
|
||||
return {
|
||||
input: toAppServerUserInput(input, manifest),
|
||||
|
|
@ -129,6 +131,9 @@ function manifestEntry(
|
|||
includedBytes,
|
||||
truncated: includedBytes < sourceBytes,
|
||||
} as const;
|
||||
if (item.attachment?.kind === "obsidian") {
|
||||
return { ...common, kind: "obsidian", inlineExcerpts: item.attachment.inlineExcerpts };
|
||||
}
|
||||
if (item.attachment?.kind !== "referencedThread") return common;
|
||||
return {
|
||||
...common,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export type TurnContextAttachment =
|
|||
truncated: boolean;
|
||||
}
|
||||
| { kind: "web" }
|
||||
| { kind: "obsidian" };
|
||||
| { kind: "obsidian"; inlineExcerpts: number };
|
||||
|
||||
export interface TurnContextManifestEntry {
|
||||
kind: TurnContextAttachment["kind"];
|
||||
|
|
@ -25,6 +25,7 @@ export interface TurnContextManifestEntry {
|
|||
includedTurns?: number;
|
||||
turnLimit?: number;
|
||||
omittedTurns?: number;
|
||||
inlineExcerpts?: number;
|
||||
}
|
||||
|
||||
export interface TurnContextManifest {
|
||||
|
|
@ -88,7 +89,15 @@ export function turnContextSubmissionId(value: string): string {
|
|||
function manifestMatchesClientId(manifest: TurnContextManifest, clientId: string | null): boolean {
|
||||
if (!clientId || !/^local-(?:user|steer)-\d+-[A-Za-z0-9_-]+-[a-z0-9]+-[a-z0-9]+$/.test(clientId)) return false;
|
||||
const submissionId = turnContextSubmissionId(clientId);
|
||||
return manifest.contexts.every((context, index) => context.id === `${submissionId}.${String(index).padStart(2, "0")}`);
|
||||
const contextIds = manifest.contexts.map((context) => context.id);
|
||||
return (
|
||||
new Set(contextIds).size === contextIds.length &&
|
||||
contextIds.every((contextId) => new RegExp(`^${escapeRegExp(submissionId)}\\.\\d{2}$`).test(contextId))
|
||||
);
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
export function referencedThreadFromManifest(manifest: TurnContextManifest | null): ReferencedThreadMetadata | null {
|
||||
|
|
@ -117,6 +126,19 @@ function manifestEntryFromUnknown(input: unknown): TurnContextManifestEntry | nu
|
|||
const truncated = value["truncated"];
|
||||
if (!kind || !id || parts === null || sourceBytes === null || includedBytes === null || typeof truncated !== "boolean") return null;
|
||||
|
||||
if (kind === "obsidian") {
|
||||
const inlineExcerpts = optionalNonNegativeInteger(value["inlineExcerpts"]);
|
||||
if (inlineExcerpts === null) return null;
|
||||
return {
|
||||
kind,
|
||||
id,
|
||||
parts,
|
||||
sourceBytes,
|
||||
includedBytes,
|
||||
truncated,
|
||||
...(inlineExcerpts === undefined ? {} : { inlineExcerpts }),
|
||||
};
|
||||
}
|
||||
if (kind !== "referencedThread") return { kind, id, parts, sourceBytes, includedBytes, truncated };
|
||||
const threadId = stringValue(value["threadId"]);
|
||||
const includedTurns = nonNegativeInteger(value["includedTurns"]);
|
||||
|
|
@ -149,6 +171,10 @@ function nonNegativeInteger(value: unknown): number | null {
|
|||
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
||||
}
|
||||
|
||||
function optionalNonNegativeInteger(value: unknown): number | null | undefined {
|
||||
return value === undefined ? undefined : nonNegativeInteger(value);
|
||||
}
|
||||
|
||||
function positiveInteger(value: unknown): number | null {
|
||||
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import {
|
|||
import {
|
||||
type ActiveNoteContextReference,
|
||||
type ComposerContextReferences,
|
||||
formatComposerContextRange,
|
||||
type SelectionContextReference,
|
||||
selectionContextReferenceMarker,
|
||||
} from "./context-references";
|
||||
|
|
@ -22,6 +21,12 @@ interface ParsedWikiLink {
|
|||
display: string;
|
||||
}
|
||||
|
||||
interface ObsidianReference {
|
||||
marker: string;
|
||||
path: string;
|
||||
excerpt?: string;
|
||||
}
|
||||
|
||||
export type WikiLinkMentionResolver = (target: string) => { name: string; path: string } | null;
|
||||
|
||||
const OBSIDIAN_CONTEXT_ADDITIONAL_CONTEXT_KEY = "codex_panel_obsidian_context";
|
||||
|
|
@ -64,7 +69,7 @@ export function preparedUserInputWithWikiLinkMentionsSkillsAndContext(
|
|||
const contextReplacement = textWithContextReferences(text, contextReferences);
|
||||
const resolvedText = contextReplacement.text;
|
||||
const mentions: RequestMention[] = [];
|
||||
const wikilinkMappings: string[] = [];
|
||||
const wikilinkReferences: ObsidianReference[] = [];
|
||||
const seenPaths = new Set<string>();
|
||||
const activeNoteSnapshots = contextReferences.activeNoteSnapshots ?? [];
|
||||
|
||||
|
|
@ -73,7 +78,7 @@ export function preparedUserInputWithWikiLinkMentionsSkillsAndContext(
|
|||
if (!mention || seenPaths.has(mention.path)) continue;
|
||||
seenPaths.add(mention.path);
|
||||
mentions.push(mention);
|
||||
wikilinkMappings.push(`- [[${link.raw}]] -> ${mention.path}`);
|
||||
wikilinkReferences.push({ marker: `[[${link.raw}]]`, path: mention.path });
|
||||
}
|
||||
|
||||
for (const selection of contextReplacement.selections) {
|
||||
|
|
@ -101,7 +106,7 @@ export function preparedUserInputWithWikiLinkMentionsSkillsAndContext(
|
|||
resolvedText,
|
||||
mentions,
|
||||
resolvedSkills,
|
||||
additionalContext(wikilinkMappings, contextReplacement.selections, attachedActiveNote),
|
||||
additionalContext(wikilinkReferences, contextReplacement.selections, attachedActiveNote),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
@ -134,63 +139,68 @@ function selectionsReferencedByText(text: string, snapshots: readonly SelectionC
|
|||
}
|
||||
|
||||
function additionalContext(
|
||||
wikilinkMappings: readonly string[],
|
||||
wikilinkReferences: readonly ObsidianReference[],
|
||||
selections: readonly SelectionContextReference[],
|
||||
activeNote: ActiveNoteContextReference | null,
|
||||
): RequestAdditionalContext[] {
|
||||
return obsidianContextAdditionalContext(wikilinkMappings, selections, activeNote);
|
||||
return obsidianContextAdditionalContext(obsidianReferences(wikilinkReferences, selections, activeNote));
|
||||
}
|
||||
|
||||
function obsidianContextAdditionalContext(
|
||||
wikilinkMappings: readonly string[],
|
||||
function obsidianReferences(
|
||||
wikilinkReferences: readonly ObsidianReference[],
|
||||
selections: readonly SelectionContextReference[],
|
||||
activeNote: ActiveNoteContextReference | null,
|
||||
): RequestAdditionalContext[] {
|
||||
if (wikilinkMappings.length === 0 && selections.length === 0 && !activeNote) return [];
|
||||
): ObsidianReference[] {
|
||||
const selectionReferences = selections.map((selection) => ({
|
||||
marker: selectionContextReferenceMarker(selection),
|
||||
path: selection.path,
|
||||
excerpt: selection.text,
|
||||
}));
|
||||
const selectedWikilinks = new Set(selections.map((selection) => referenceKey(`[[${selection.linktext}]]`, selection.path)));
|
||||
return [
|
||||
...wikilinkReferences.filter((reference) => !selectedWikilinks.has(referenceKey(reference.marker, reference.path))),
|
||||
...selectionReferences,
|
||||
...(activeNote ? [{ marker: ACTIVE_FILE_MENTION_NAME, path: activeNote.path }] : []),
|
||||
];
|
||||
}
|
||||
|
||||
function referenceKey(marker: string, path: string): string {
|
||||
return `${marker}\u0000${path}`;
|
||||
}
|
||||
|
||||
function obsidianContextAdditionalContext(references: readonly ObsidianReference[]): RequestAdditionalContext[] {
|
||||
if (references.length === 0) return [];
|
||||
const inlineExcerpts = references.filter((reference) => reference.excerpt !== undefined).length;
|
||||
return [
|
||||
{
|
||||
key: OBSIDIAN_CONTEXT_ADDITIONAL_CONTEXT_KEY,
|
||||
kind: "untrusted",
|
||||
value: obsidianContextValue(wikilinkMappings, selections, activeNote),
|
||||
value: obsidianContextValue(references),
|
||||
...(inlineExcerpts > 0 ? { attachment: { kind: "obsidian" as const, inlineExcerpts } } : {}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function obsidianContextValue(
|
||||
wikilinkMappings: readonly string[],
|
||||
selections: readonly SelectionContextReference[],
|
||||
activeNote: ActiveNoteContextReference | null,
|
||||
): string {
|
||||
const sections: string[][] = [];
|
||||
if (wikilinkMappings.length > 0) sections.push(["Resolved wikilinks:", ...wikilinkMappings]);
|
||||
if (selections.length > 0) {
|
||||
sections.push(["Referenced selections:", ...selectionReferenceLines(selections), "", ...selectionBodyLines(selections)]);
|
||||
}
|
||||
if (activeNote) sections.push(activeNoteContextLines(activeNote));
|
||||
function obsidianContextValue(references: readonly ObsidianReference[]): string {
|
||||
const excerpts = references.filter((reference): reference is ObsidianReference & { excerpt: string } => reference.excerpt !== undefined);
|
||||
return [
|
||||
"Obsidian context for the current user input:",
|
||||
...sections.flatMap((section, index) => (index === 0 ? section : ["", ...section])),
|
||||
"Obsidian references for the current user input:",
|
||||
...references.map(referenceLine),
|
||||
...(excerpts.length > 0 ? ["", "Inline excerpts:", ...excerptLines(excerpts)] : []),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function selectionReferenceLines(selections: readonly SelectionContextReference[]): string[] {
|
||||
return selections.map((selection) => {
|
||||
const location = `${selection.path} ${formatComposerContextRange(selection.range)}`;
|
||||
return `- ${selectionContextReferenceMarker(selection)} -> ${location}`;
|
||||
});
|
||||
function referenceLine(reference: ObsidianReference): string {
|
||||
return `- ${reference.marker} -> ${reference.path}${reference.excerpt === undefined ? "" : " (inline excerpt below)"}`;
|
||||
}
|
||||
|
||||
function selectionBodyLines(selections: readonly SelectionContextReference[]): string[] {
|
||||
return selections.flatMap((selection, index) => {
|
||||
function excerptLines(references: readonly (ObsidianReference & { excerpt: string })[]): string[] {
|
||||
return references.flatMap((reference, index) => {
|
||||
const prefix = index === 0 ? [] : [""];
|
||||
return [...prefix, `${selectionContextReferenceMarker(selection)}:`, selection.text];
|
||||
return [...prefix, `${reference.marker}:`, reference.excerpt];
|
||||
});
|
||||
}
|
||||
|
||||
function activeNoteContextLines(activeNote: ActiveNoteContextReference): string[] {
|
||||
return ["Referenced active file:", `- ${ACTIVE_FILE_MENTION_NAME} -> ${activeNote.path}`];
|
||||
}
|
||||
|
||||
function parsedSkillReferences(text: string): string[] {
|
||||
const references: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@ export function contextAttachmentsFromManifest(manifest: TurnContextManifest | n
|
|||
const source = visibleWebSource(visibleText);
|
||||
attachments.push({ label: web.truncated ? "Web page (truncated)" : "Web page", ...(source ? { detail: source } : {}) });
|
||||
}
|
||||
if (manifest?.contexts.some((context) => context.kind === "obsidian" && context.truncated)) {
|
||||
attachments.push({ label: "Obsidian context (truncated)" });
|
||||
const obsidian = manifest?.contexts.find((context) => context.kind === "obsidian" && context.truncated);
|
||||
if (obsidian) {
|
||||
attachments.push({ label: obsidian.inlineExcerpts ? "Obsidian excerpt (truncated)" : "Obsidian context (truncated)" });
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,37 @@ describe("app-server request input", () => {
|
|||
expect(manifestInput?.type === "text" ? manifestInput.text : "").not.toContain("本文です");
|
||||
});
|
||||
|
||||
it("keeps Obsidian reference metadata ahead of a truncated inline excerpt", () => {
|
||||
const reference = "[[Note]] (L2:C1-L3:C1) -> Note.md";
|
||||
const value = [
|
||||
"Obsidian references for the current user input:",
|
||||
`- ${reference} (inline excerpt below)`,
|
||||
"",
|
||||
"Inline excerpts:",
|
||||
"[[Note]] (L2:C1-L3:C1):",
|
||||
"本文".repeat(20_000),
|
||||
].join("\n");
|
||||
const prepared = appServerTurnInputFromCodexInput(
|
||||
[
|
||||
{ type: "text", text: "review it" },
|
||||
{
|
||||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value,
|
||||
attachment: { kind: "obsidian", inlineExcerpts: 1 },
|
||||
},
|
||||
],
|
||||
"local-user",
|
||||
);
|
||||
|
||||
const firstPart = Object.values(prepared.additionalContext ?? {})[0];
|
||||
expect(firstPart?.value).toContain(reference);
|
||||
const manifestInput = prepared.input.at(-1);
|
||||
const manifest = manifestInput?.type === "text" ? turnContextManifestFromText(manifestInput.text) : null;
|
||||
expect(manifest?.contexts).toEqual([expect.objectContaining({ kind: "obsidian", inlineExcerpts: 1, truncated: true })]);
|
||||
});
|
||||
|
||||
it("namespaces identical explicit context by submission", () => {
|
||||
const input: CodexInput = [
|
||||
{ type: "text", text: "read it" },
|
||||
|
|
@ -134,6 +165,6 @@ describe("app-server request input", () => {
|
|||
expect(entries.some(([key, entry]) => key.includes(".selection.") && entry.value.includes("selected text"))).toBe(true);
|
||||
const manifestInput = prepared.input.at(-1);
|
||||
const manifest = manifestInput?.type === "text" ? turnContextManifestFromText(manifestInput.text) : null;
|
||||
expect(manifest?.contexts.map((context) => context.parts)).toEqual([7, 1]);
|
||||
expect(manifest?.contexts.map((context) => context.parts)).toEqual([7]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -176,6 +176,41 @@ describe("turn item conversion preserves app-server semantics", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("accepts a persisted attachment descriptor after an unpersisted reference context", () => {
|
||||
const clientId = "local-user-1-seed-1-1";
|
||||
const prepared = appServerTurnInputFromCodexInput(
|
||||
[
|
||||
{ type: "text", text: "https://example.com/ compare with [[Note]]" },
|
||||
{
|
||||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value: "- [[Note]] -> Note.md",
|
||||
},
|
||||
{
|
||||
type: "additionalContext",
|
||||
key: "codex_panel_web_context",
|
||||
kind: "untrusted",
|
||||
value: "Source: https://example.com/\n\npage",
|
||||
attachment: { kind: "web" },
|
||||
},
|
||||
],
|
||||
clientId,
|
||||
);
|
||||
|
||||
expect(
|
||||
threadStreamItemFromTurnItem({
|
||||
type: "userMessage",
|
||||
id: "u1",
|
||||
clientId,
|
||||
content: prepared.input,
|
||||
}),
|
||||
).toMatchObject({
|
||||
text: "https://example.com/ compare with [[Note]]",
|
||||
contextAttachments: [{ label: "Web page", detail: "https://example.com/" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("does not hide a user-authored manifest-like message", () => {
|
||||
const text =
|
||||
'[Codex Panel context v2]{"version":2,"contexts":[{"kind":"web","id":"fake","parts":1,"sourceBytes":1,"includedBytes":1,"truncated":false}]}';
|
||||
|
|
@ -205,12 +240,18 @@ describe("turn item conversion preserves app-server semantics", () => {
|
|||
expect(projected).not.toHaveProperty("contextAttachments");
|
||||
});
|
||||
|
||||
it("surfaces truncated Obsidian context from the persisted manifest", () => {
|
||||
it("surfaces a truncated Obsidian excerpt from the persisted manifest", () => {
|
||||
const clientId = "local-user-1-seed-1-1";
|
||||
const prepared = appServerTurnInputFromCodexInput(
|
||||
[
|
||||
{ type: "text", text: "review the selection" },
|
||||
{ type: "additionalContext", key: "codex_panel_obsidian_context", kind: "untrusted", value: "x".repeat(30_000) },
|
||||
{
|
||||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value: "x".repeat(30_000),
|
||||
attachment: { kind: "obsidian", inlineExcerpts: 1 },
|
||||
},
|
||||
],
|
||||
clientId,
|
||||
);
|
||||
|
|
@ -224,7 +265,7 @@ describe("turn item conversion preserves app-server semantics", () => {
|
|||
}),
|
||||
).toMatchObject({
|
||||
text: "review the selection",
|
||||
contextAttachments: [{ label: "Obsidian context (truncated)" }],
|
||||
contextAttachments: [{ label: "Obsidian excerpt (truncated)" }],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ describe("chat app-server transports", () => {
|
|||
{ type: "text", text, text_elements: [] },
|
||||
{ type: "mention", name: "Alpha", path: "notes/Alpha.md" },
|
||||
{ type: "mention", name: "<active>", path: "notes/Alpha.md" },
|
||||
{ type: "text", text: expect.stringContaining("[Codex Panel context v2]"), text_elements: [] },
|
||||
],
|
||||
clientUserMessageId: "local-user",
|
||||
});
|
||||
|
|
@ -142,7 +141,7 @@ describe("chat app-server transports", () => {
|
|||
"codex_panel.local-user.00.codex_panel_obsidian_context.part_01_of_01": {
|
||||
kind: "untrusted",
|
||||
value:
|
||||
"Codex Panel context part 1/1.\nSource: codex_panel_obsidian_context\n\nObsidian context for the current user input:\nResolved wikilinks:\n- [[Alpha]] -> notes/Alpha.md\n\nReferenced active file:\n- <active> -> notes/Alpha.md",
|
||||
"Codex Panel context part 1/1.\nSource: codex_panel_obsidian_context\n\nObsidian references for the current user input:\n- [[Alpha]] -> notes/Alpha.md\n- <active> -> notes/Alpha.md",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ describe("composer suggestions", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value: "Obsidian context for the current user input:\nResolved wikilinks:\n- [[Assets/Diagram.png]] -> Assets/Diagram.png",
|
||||
value: "Obsidian references for the current user input:\n- [[Assets/Diagram.png]] -> Assets/Diagram.png",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ const obsidianContext = (...sections: string[]) => ({
|
|||
type: "additionalContext" as const,
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted" as const,
|
||||
value: ["Obsidian context for the current user input:", ...sections].join("\n"),
|
||||
value: ["Obsidian references for the current user input:", ...sections].join("\n"),
|
||||
});
|
||||
|
||||
const wikilinkContext = (...mappings: string[]) => obsidianContext("Resolved wikilinks:", ...mappings);
|
||||
const wikilinkContext = (...mappings: string[]) => obsidianContext(...mappings);
|
||||
|
||||
function userInputWithWikiLinkMentionsAndSkills(
|
||||
text: string,
|
||||
|
|
@ -226,7 +226,7 @@ describe("wikilink context", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value: "Obsidian context for the current user input:\nReferenced active file:\n- <active> -> notes/Alpha.md",
|
||||
value: "Obsidian references for the current user input:\n- <active> -> notes/Alpha.md",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -253,8 +253,7 @@ describe("wikilink context", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value:
|
||||
"Obsidian context for the current user input:\nResolved wikilinks:\n- [[Beta]] -> notes/Beta.md\n\nReferenced active file:\n- <active> -> notes/Alpha.md",
|
||||
value: "Obsidian references for the current user input:\n- [[Beta]] -> notes/Beta.md\n- <active> -> notes/Alpha.md",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -294,8 +293,9 @@ describe("wikilink context", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
attachment: { kind: "obsidian", inlineExcerpts: 1 },
|
||||
value:
|
||||
"Obsidian context for the current user input:\nResolved wikilinks:\n- [[Beta]] -> notes/Beta.md\n- [[Gamma]] -> notes/Gamma.md\n\nReferenced selections:\n- [[Gamma]] (L2:C1-L2:C6) -> notes/Gamma.md L2:C1-L2:C6\n\n[[Gamma]] (L2:C1-L2:C6):\nselected gamma\n\nReferenced active file:\n- <active> -> notes/Alpha.md",
|
||||
"Obsidian references for the current user input:\n- [[Beta]] -> notes/Beta.md\n- [[Gamma]] (L2:C1-L2:C6) -> notes/Gamma.md (inline excerpt below)\n- <active> -> notes/Alpha.md\n\nInline excerpts:\n[[Gamma]] (L2:C1-L2:C6):\nselected gamma",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -331,8 +331,9 @@ describe("wikilink context", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
attachment: { kind: "obsidian", inlineExcerpts: 1 },
|
||||
value:
|
||||
"Obsidian context for the current user input:\nResolved wikilinks:\n- [[notes/Alpha]] -> notes/Alpha.md\n\nReferenced selections:\n- [[notes/Alpha]] (L42:C5-L47:C1) -> notes/Alpha.md L42:C5-L47:C1\n\n[[notes/Alpha]] (L42:C5-L47:C1):\ncompleted selection",
|
||||
"Obsidian references for the current user input:\n- [[notes/Alpha]] (L42:C5-L47:C1) -> notes/Alpha.md (inline excerpt below)\n\nInline excerpts:\n[[notes/Alpha]] (L42:C5-L47:C1):\ncompleted selection",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1148,7 +1148,7 @@ describe("ChatComposerController", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value: "Obsidian context for the current user input:\nReferenced active file:\n- <active> -> notes/Alpha.md",
|
||||
value: "Obsidian references for the current user input:\n- <active> -> notes/Alpha.md",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
@ -1220,8 +1220,9 @@ describe("ChatComposerController", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
attachment: { kind: "obsidian", inlineExcerpts: 1 },
|
||||
value:
|
||||
"Obsidian context for the current user input:\nResolved wikilinks:\n- [[notes/Alpha]] -> notes/Alpha.md\n\nReferenced selections:\n- [[notes/Alpha]] (L42:C5-L47:C1) -> notes/Alpha.md L42:C5-L47:C1\n\n[[notes/Alpha]] (L42:C5-L47:C1):\ninitial selection",
|
||||
"Obsidian references for the current user input:\n- [[notes/Alpha]] (L42:C5-L47:C1) -> notes/Alpha.md (inline excerpt below)\n\nInline excerpts:\n[[notes/Alpha]] (L42:C5-L47:C1):\ninitial selection",
|
||||
});
|
||||
|
||||
controller.setDraft("", { clearSuggestions: true });
|
||||
|
|
@ -1229,14 +1230,15 @@ describe("ChatComposerController", () => {
|
|||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
attachment: { kind: "obsidian", inlineExcerpts: 1 },
|
||||
value:
|
||||
"Obsidian context for the current user input:\nResolved wikilinks:\n- [[notes/Alpha]] -> notes/Alpha.md\n\nReferenced selections:\n- [[notes/Alpha]] (L42:C5-L47:C1) -> notes/Alpha.md L42:C5-L47:C1\n\n[[notes/Alpha]] (L42:C5-L47:C1):\ninitial selection",
|
||||
"Obsidian references for the current user input:\n- [[notes/Alpha]] (L42:C5-L47:C1) -> notes/Alpha.md (inline excerpt below)\n\nInline excerpts:\n[[notes/Alpha]] (L42:C5-L47:C1):\ninitial selection",
|
||||
});
|
||||
expect(controller.preparedInput(completedSelectionReference).input).toContainEqual({
|
||||
type: "additionalContext",
|
||||
key: "codex_panel_obsidian_context",
|
||||
kind: "untrusted",
|
||||
value: "Obsidian context for the current user input:\nResolved wikilinks:\n- [[notes/Alpha]] -> notes/Alpha.md",
|
||||
value: "Obsidian references for the current user input:\n- [[notes/Alpha]] -> notes/Alpha.md",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue