@theia/ai-core 1.74.0-next.5 → 1.74.0-next.8
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/lib/browser/frontend-prompt-customization-service.d.ts +38 -0
- package/lib/browser/frontend-prompt-customization-service.d.ts.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.js +189 -76
- package/lib/browser/frontend-prompt-customization-service.js.map +1 -1
- package/lib/browser/frontend-prompt-customization-service.spec.js +103 -0
- package/lib/browser/frontend-prompt-customization-service.spec.js.map +1 -1
- package/lib/common/prompt-service.d.ts +13 -3
- package/lib/common/prompt-service.d.ts.map +1 -1
- package/lib/common/prompt-service.js.map +1 -1
- package/package.json +9 -9
- package/src/browser/frontend-prompt-customization-service.spec.ts +130 -1
- package/src/browser/frontend-prompt-customization-service.ts +204 -84
- package/src/common/prompt-service.ts +14 -3
|
@@ -1669,7 +1669,7 @@ export class DefaultPromptFragmentCustomizationService implements PromptFragment
|
|
|
1669
1669
|
|
|
1670
1670
|
try {
|
|
1671
1671
|
const fileContent = await this.fileService.read(customAgentYamlUri, { encoding: 'utf-8' });
|
|
1672
|
-
const doc =
|
|
1672
|
+
const doc = this.parseCustomAgentsYaml(fileContent.value);
|
|
1673
1673
|
|
|
1674
1674
|
if (!Array.isArray(doc) || !doc.every(entry => CustomAgentDescription.is(entry))) {
|
|
1675
1675
|
this.logger.debug(`Invalid customAgents.yml file content in ${directoryURI.toString()}`);
|
|
@@ -1736,6 +1736,47 @@ export class DefaultPromptFragmentCustomizationService implements PromptFragment
|
|
|
1736
1736
|
return fileURI;
|
|
1737
1737
|
}
|
|
1738
1738
|
|
|
1739
|
+
/**
|
|
1740
|
+
* Parse a `customAgents.yml` document, preserving newlines in folded-block-scalar prompts.
|
|
1741
|
+
*
|
|
1742
|
+
* `js-yaml` folds the single newlines of a folded scalar (`>` / `>-`) into spaces, which merges
|
|
1743
|
+
* a markdown heading (`## Task`) into the paragraph below it. Markdown is newline-sensitive, so
|
|
1744
|
+
* we re-interpret folded `prompt:` scalars as literal (`|`) before parsing. Only the block-scalar
|
|
1745
|
+
* header is rewritten; quoted, plain and already-literal values are left untouched.
|
|
1746
|
+
*
|
|
1747
|
+
* Known limitation: prompts stored as a plain or quoted multi-line scalar (rare) still fold.
|
|
1748
|
+
*/
|
|
1749
|
+
protected parseCustomAgentsYaml(content: string): unknown {
|
|
1750
|
+
return load(this.unfoldPromptBlockScalars(content));
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Rewrite a folded `prompt:` block-scalar header (`>`, `>-`, `>+`, `>2-`, ...) to its literal
|
|
1755
|
+
* equivalent (`|`...). The lookahead ensures only a block-scalar header is matched (indentation
|
|
1756
|
+
* and chomping indicators plus an optional trailing comment), so plain/quoted prompt values and
|
|
1757
|
+
* any `>` inside the prompt body are never touched.
|
|
1758
|
+
*/
|
|
1759
|
+
protected unfoldPromptBlockScalars(content: string): string {
|
|
1760
|
+
return content.replace(/^([ \t]*prompt:[ \t]*)>(?=[-+0-9]*[ \t]*(?:#.*)?$)/mg, '$1|');
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* Returns `true` if migration would write anything: a scope still holds a legacy
|
|
1765
|
+
* `customAgents.yml`, or a previously migrated scope has `agent.md` files that can be corrected
|
|
1766
|
+
* (see {@link computeScopeCorrections}). Read-only; used to decide whether to prompt the user.
|
|
1767
|
+
*/
|
|
1768
|
+
async hasPendingCustomAgentMigration(): Promise<boolean> {
|
|
1769
|
+
for (const scope of await this.getCustomAgentScopes()) {
|
|
1770
|
+
if (await this.fileService.exists(scope.resolve('customAgents.yml'))) {
|
|
1771
|
+
return true;
|
|
1772
|
+
}
|
|
1773
|
+
if ((await this.computeScopeCorrections(scope)).length > 0) {
|
|
1774
|
+
return true;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1739
1780
|
/**
|
|
1740
1781
|
* Auto-migrate every legacy `customAgents.yml` reachable from the configured scopes to the new
|
|
1741
1782
|
* `agents/<id>/agent.md` layout. The original content is never deleted:
|
|
@@ -1769,7 +1810,7 @@ export class DefaultPromptFragmentCustomizationService implements PromptFragment
|
|
|
1769
1810
|
reports.push(report);
|
|
1770
1811
|
}
|
|
1771
1812
|
}
|
|
1772
|
-
if (reports.some(r => r.migrated > 0)) {
|
|
1813
|
+
if (reports.some(r => r.migrated > 0 || r.corrected > 0)) {
|
|
1773
1814
|
this.onDidChangeCustomAgentsEmitter.fire();
|
|
1774
1815
|
}
|
|
1775
1816
|
return reports;
|
|
@@ -1777,116 +1818,193 @@ export class DefaultPromptFragmentCustomizationService implements PromptFragment
|
|
|
1777
1818
|
|
|
1778
1819
|
protected async migrateSingleScope(scopeDir: URI): Promise<MigrationReport | undefined> {
|
|
1779
1820
|
const yamlURI = scopeDir.resolve('customAgents.yml');
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
}
|
|
1783
|
-
this.logger.info(`Migrating custom agents from ${yamlURI.toString()}`);
|
|
1784
|
-
let entries: CustomAgentDescription[];
|
|
1785
|
-
try {
|
|
1786
|
-
const fileContent = await this.fileService.read(yamlURI, { encoding: 'utf-8' });
|
|
1787
|
-
const doc = load(fileContent.value);
|
|
1788
|
-
if (!Array.isArray(doc) || !doc.every(CustomAgentDescription.is)) {
|
|
1789
|
-
this.logger.warn(`Skipping migration of ${yamlURI.toString()}: file content is not a valid CustomAgentDescription[]`);
|
|
1790
|
-
return { scope: scopeDir, yamlURI, migrated: 0, alreadyPresent: 0, failed: 0, yamlBackedUp: false, promptOverridesMigrated: 0 };
|
|
1791
|
-
}
|
|
1792
|
-
entries = doc;
|
|
1793
|
-
} catch (e) {
|
|
1794
|
-
this.logger.warn(`Skipping migration of ${yamlURI.toString()}: ${e.message}`);
|
|
1795
|
-
return { scope: scopeDir, yamlURI, migrated: 0, alreadyPresent: 0, failed: 0, yamlBackedUp: false, promptOverridesMigrated: 0 };
|
|
1796
|
-
}
|
|
1821
|
+
const backupURI = scopeDir.resolve('customAgents.yml.bak');
|
|
1822
|
+
const yamlExists = await this.fileService.exists(yamlURI);
|
|
1797
1823
|
|
|
1798
1824
|
let migrated = 0;
|
|
1799
1825
|
let alreadyPresent = 0;
|
|
1800
1826
|
let failed = 0;
|
|
1801
1827
|
let promptOverridesMigrated = 0;
|
|
1828
|
+
let yamlBackedUp = false;
|
|
1802
1829
|
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1830
|
+
const emptyReport = (): MigrationReport => ({
|
|
1831
|
+
scope: scopeDir, yamlURI, migrated: 0, alreadyPresent: 0, failed: 0,
|
|
1832
|
+
yamlBackedUp: false, promptOverridesMigrated: 0, corrected: 0
|
|
1833
|
+
});
|
|
1806
1834
|
|
|
1807
|
-
|
|
1808
|
-
|
|
1835
|
+
if (yamlExists) {
|
|
1836
|
+
this.logger.info(`Migrating custom agents from ${yamlURI.toString()}`);
|
|
1837
|
+
let entries: CustomAgentDescription[];
|
|
1838
|
+
try {
|
|
1839
|
+
const fileContent = await this.fileService.read(yamlURI, { encoding: 'utf-8' });
|
|
1840
|
+
const doc = this.parseCustomAgentsYaml(fileContent.value);
|
|
1841
|
+
if (!Array.isArray(doc) || !doc.every(CustomAgentDescription.is)) {
|
|
1842
|
+
this.logger.warn(`Skipping migration of ${yamlURI.toString()}: file content is not a valid CustomAgentDescription[]`);
|
|
1843
|
+
return emptyReport();
|
|
1844
|
+
}
|
|
1845
|
+
entries = doc;
|
|
1846
|
+
} catch (e) {
|
|
1847
|
+
this.logger.warn(`Skipping migration of ${yamlURI.toString()}: ${e.message}`);
|
|
1848
|
+
return emptyReport();
|
|
1849
|
+
}
|
|
1809
1850
|
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
const seenNames = new Set<string>();
|
|
1851
|
+
// A previous failed migration may have created `agents/<id>/` directories without an
|
|
1852
|
+
// `agent.md` inside. Remove those empty placeholders so this run can recreate them cleanly.
|
|
1853
|
+
await this.cleanupEmptyAgentFolders(scopeDir);
|
|
1814
1854
|
|
|
1815
|
-
|
|
1816
|
-
const
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1855
|
+
// Snapshot scope-root template files once so we can match per agent without re-listing.
|
|
1856
|
+
const scopeRootTemplateFiles = await this.listScopeRootPromptTemplates(scopeDir);
|
|
1857
|
+
|
|
1858
|
+
// Prompt-fragment files are matched by agent *name* (the fragment id is `<name>_prompt`)
|
|
1859
|
+
// while folders are keyed by the unique *id*. When two entries share a name, only the first
|
|
1860
|
+
// can own the name-based files, so track seen names and skip the move for the duplicates.
|
|
1861
|
+
const seenNames = new Set<string>();
|
|
1862
|
+
|
|
1863
|
+
for (const entry of entries) {
|
|
1864
|
+
const agentFolderURI = scopeDir.resolve(CUSTOM_AGENTS_DIRECTORY).resolve(entry.id);
|
|
1865
|
+
const targetURI = agentFolderURI.resolve(CUSTOM_AGENT_FILE_NAME);
|
|
1866
|
+
if (await this.fileService.exists(targetURI)) {
|
|
1867
|
+
alreadyPresent++;
|
|
1868
|
+
} else {
|
|
1869
|
+
try {
|
|
1870
|
+
await this.fileService.createFile(
|
|
1871
|
+
targetURI,
|
|
1872
|
+
BinaryBuffer.fromString(serializeCustomAgentFile(entry)),
|
|
1873
|
+
{ overwrite: true }
|
|
1874
|
+
);
|
|
1875
|
+
migrated++;
|
|
1876
|
+
} catch (e) {
|
|
1877
|
+
this.logger.warn(`Failed to migrate agent '${entry.id}' from ${yamlURI.toString()}: ${e?.message ?? e}`, e);
|
|
1878
|
+
failed++;
|
|
1879
|
+
continue;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
if (seenNames.has(entry.name)) {
|
|
1884
|
+
this.logger.warn(
|
|
1885
|
+
`Multiple custom agents in ${yamlURI.toString()} share the name '${entry.name}'; ` +
|
|
1886
|
+
'prompt fragment files were migrated to the first matching agent only.'
|
|
1826
1887
|
);
|
|
1827
|
-
migrated++;
|
|
1828
|
-
} catch (e) {
|
|
1829
|
-
this.logger.warn(`Failed to migrate agent '${entry.id}' from ${yamlURI.toString()}: ${e?.message ?? e}`, e);
|
|
1830
|
-
failed++;
|
|
1831
1888
|
continue;
|
|
1832
1889
|
}
|
|
1890
|
+
seenNames.add(entry.name);
|
|
1891
|
+
|
|
1892
|
+
// Move any scope-root .prompttemplate files that look like they belong to this agent
|
|
1893
|
+
// (filename stem starts with `<name>_prompt`, followed by EOF or a separator).
|
|
1894
|
+
// They become variants under `<scope>/agents/<id>/`.
|
|
1895
|
+
for (const file of scopeRootTemplateFiles) {
|
|
1896
|
+
if (!matchesAgentPromptPrefix(file.stem, entry.name)) {
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
const destURI = agentFolderURI.resolve(file.uri.path.base);
|
|
1900
|
+
if (await this.fileService.exists(destURI)) {
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
try {
|
|
1904
|
+
await this.fileService.move(file.uri, destURI);
|
|
1905
|
+
promptOverridesMigrated++;
|
|
1906
|
+
} catch (e) {
|
|
1907
|
+
this.logger.warn(`Failed to move prompt fragment ${file.uri.toString()} into ${destURI.toString()}: ${e?.message ?? e}`);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1833
1910
|
}
|
|
1834
1911
|
|
|
1835
|
-
if (
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
)
|
|
1840
|
-
|
|
1841
|
-
}
|
|
1842
|
-
seenNames.add(entry.name);
|
|
1843
|
-
|
|
1844
|
-
// Move any scope-root .prompttemplate files that look like they belong to this agent
|
|
1845
|
-
// (filename stem starts with `<name>_prompt`, followed by EOF or a separator).
|
|
1846
|
-
// They become variants under `<scope>/agents/<id>/`.
|
|
1847
|
-
for (const file of scopeRootTemplateFiles) {
|
|
1848
|
-
if (!matchesAgentPromptPrefix(file.stem, entry.name)) {
|
|
1849
|
-
continue;
|
|
1850
|
-
}
|
|
1851
|
-
const destURI = agentFolderURI.resolve(file.uri.path.base);
|
|
1852
|
-
if (await this.fileService.exists(destURI)) {
|
|
1853
|
-
continue;
|
|
1912
|
+
if (failed === 0) {
|
|
1913
|
+
try {
|
|
1914
|
+
await this.fileService.move(yamlURI, backupURI, { overwrite: true });
|
|
1915
|
+
yamlBackedUp = true;
|
|
1916
|
+
} catch (e) {
|
|
1917
|
+
this.logger.warn(`Migrated ${migrated} agents but failed to back up ${yamlURI.toString()}: ${e.message}`);
|
|
1854
1918
|
}
|
|
1919
|
+
} else {
|
|
1855
1920
|
try {
|
|
1856
|
-
await this.fileService.
|
|
1857
|
-
|
|
1921
|
+
if (!(await this.fileService.exists(backupURI))) {
|
|
1922
|
+
await this.fileService.move(yamlURI, backupURI);
|
|
1923
|
+
yamlBackedUp = true;
|
|
1924
|
+
}
|
|
1858
1925
|
} catch (e) {
|
|
1859
|
-
this.logger.warn(`Failed to
|
|
1926
|
+
this.logger.warn(`Failed to back up ${yamlURI.toString()} to ${backupURI.toString()}: ${e.message}`);
|
|
1860
1927
|
}
|
|
1861
1928
|
}
|
|
1862
1929
|
}
|
|
1863
1930
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1931
|
+
// Correct any `agent.md` left with merged headings by an earlier (v1.73.0) migration. Anything
|
|
1932
|
+
// just migrated above is already correct, so only stale files from a previous run are rewritten.
|
|
1933
|
+
let corrected = 0;
|
|
1934
|
+
for (const { uri, content } of await this.computeScopeCorrections(scopeDir)) {
|
|
1867
1935
|
try {
|
|
1868
|
-
await this.fileService.
|
|
1869
|
-
|
|
1936
|
+
await this.fileService.createFile(uri, BinaryBuffer.fromString(content), { overwrite: true });
|
|
1937
|
+
corrected++;
|
|
1870
1938
|
} catch (e) {
|
|
1871
|
-
this.logger.warn(`
|
|
1872
|
-
}
|
|
1873
|
-
} else {
|
|
1874
|
-
try {
|
|
1875
|
-
if (!(await this.fileService.exists(backupURI))) {
|
|
1876
|
-
await this.fileService.move(yamlURI, backupURI);
|
|
1877
|
-
yamlBackedUp = true;
|
|
1878
|
-
}
|
|
1879
|
-
} catch (e) {
|
|
1880
|
-
this.logger.warn(`Failed to back up ${yamlURI.toString()} to ${backupURI.toString()}: ${e.message}`);
|
|
1939
|
+
this.logger.warn(`Failed to correct migrated agent file ${uri.toString()}: ${e?.message ?? e}`);
|
|
1881
1940
|
}
|
|
1882
1941
|
}
|
|
1883
1942
|
|
|
1943
|
+
if (!yamlExists && corrected === 0) {
|
|
1944
|
+
return undefined; // nothing to migrate and nothing to correct in this scope
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1884
1947
|
this.logger.info(
|
|
1885
|
-
`Migration done for ${
|
|
1886
|
-
`
|
|
1887
|
-
`yamlBackedUp=${yamlBackedUp}, promptOverridesMigrated=${promptOverridesMigrated}`
|
|
1948
|
+
`Migration done for ${scopeDir.toString()}: migrated=${migrated}, alreadyPresent=${alreadyPresent}, ` +
|
|
1949
|
+
`failed=${failed}, yamlBackedUp=${yamlBackedUp}, promptOverridesMigrated=${promptOverridesMigrated}, corrected=${corrected}`
|
|
1888
1950
|
);
|
|
1889
|
-
return { scope: scopeDir, yamlURI, migrated, alreadyPresent, failed, yamlBackedUp, promptOverridesMigrated };
|
|
1951
|
+
return { scope: scopeDir, yamlURI, migrated, alreadyPresent, failed, yamlBackedUp, promptOverridesMigrated, corrected };
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
/**
|
|
1955
|
+
* Compute the `agent.md` rewrites needed to recover headings folded by an earlier (v1.73.0)
|
|
1956
|
+
* migration. The backup (`customAgents.yml.bak`) is the source that migration used: parsing it
|
|
1957
|
+
* the old (folded) way reproduces the exact bytes it wrote, and parsing it the new (literal) way
|
|
1958
|
+
* produces the corrected content. Only files still byte-identical to the buggy output are eligible,
|
|
1959
|
+
* so files the user edited (and already-correct files) are left untouched. Read-only; used both to
|
|
1960
|
+
* detect pending work and to perform the rewrite.
|
|
1961
|
+
*/
|
|
1962
|
+
protected async computeScopeCorrections(scopeDir: URI): Promise<Array<{ uri: URI; content: string }>> {
|
|
1963
|
+
const backupURI = scopeDir.resolve('customAgents.yml.bak');
|
|
1964
|
+
if (!(await this.fileService.exists(backupURI))) {
|
|
1965
|
+
return [];
|
|
1966
|
+
}
|
|
1967
|
+
let oldEntries: unknown;
|
|
1968
|
+
let newEntries: unknown;
|
|
1969
|
+
try {
|
|
1970
|
+
const raw = (await this.fileService.read(backupURI, { encoding: 'utf-8' })).value;
|
|
1971
|
+
oldEntries = load(raw); // reproduces the folded (buggy) output
|
|
1972
|
+
newEntries = this.parseCustomAgentsYaml(raw); // produces the heading-preserving fix
|
|
1973
|
+
} catch {
|
|
1974
|
+
return [];
|
|
1975
|
+
}
|
|
1976
|
+
if (!Array.isArray(oldEntries) || !oldEntries.every(CustomAgentDescription.is)
|
|
1977
|
+
|| !Array.isArray(newEntries) || !newEntries.every(CustomAgentDescription.is)) {
|
|
1978
|
+
return [];
|
|
1979
|
+
}
|
|
1980
|
+
const fixedById = new Map((newEntries as CustomAgentDescription[]).map(e => [e.id, e] as const));
|
|
1981
|
+
const corrections: Array<{ uri: URI; content: string }> = [];
|
|
1982
|
+
for (const oldEntry of oldEntries as CustomAgentDescription[]) {
|
|
1983
|
+
const fixed = fixedById.get(oldEntry.id);
|
|
1984
|
+
if (!fixed) {
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
const buggyContent = serializeCustomAgentFile(oldEntry);
|
|
1988
|
+
const fixedContent = serializeCustomAgentFile(fixed);
|
|
1989
|
+
if (buggyContent === fixedContent) {
|
|
1990
|
+
continue; // prompt had no folded headings to recover
|
|
1991
|
+
}
|
|
1992
|
+
const agentMdURI = scopeDir.resolve(CUSTOM_AGENTS_DIRECTORY).resolve(oldEntry.id).resolve(CUSTOM_AGENT_FILE_NAME);
|
|
1993
|
+
if (!(await this.fileService.exists(agentMdURI))) {
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
let current: string;
|
|
1997
|
+
try {
|
|
1998
|
+
current = (await this.fileService.read(agentMdURI, { encoding: 'utf-8' })).value;
|
|
1999
|
+
} catch {
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
// Only rewrite files untouched since migration; current bytes still matching the buggy output.
|
|
2003
|
+
if (current === buggyContent) {
|
|
2004
|
+
corrections.push({ uri: agentMdURI, content: fixedContent });
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
return corrections;
|
|
1890
2008
|
}
|
|
1891
2009
|
|
|
1892
2010
|
/**
|
|
@@ -1923,6 +2041,8 @@ export interface MigrationReport {
|
|
|
1923
2041
|
yamlBackedUp: boolean;
|
|
1924
2042
|
/** Number of scope-root prompt customization files (`<name>_prompt.prompttemplate`) folded into agent.md and deleted. */
|
|
1925
2043
|
promptOverridesMigrated: number;
|
|
2044
|
+
/** Number of already-migrated `agent.md` files rewritten to recover folded headings. */
|
|
2045
|
+
corrected: number;
|
|
1926
2046
|
}
|
|
1927
2047
|
|
|
1928
2048
|
/**
|
|
@@ -385,11 +385,21 @@ export interface PromptFragmentCustomizationService {
|
|
|
385
385
|
createCustomAgentFile(parentDirectory: URI, agent: CustomAgentDescription): Promise<URI>;
|
|
386
386
|
|
|
387
387
|
/**
|
|
388
|
-
*
|
|
388
|
+
* Returns `true` if migration would write anything: a scope still holds a legacy
|
|
389
|
+
* `customAgents.yml`, or a previously migrated scope has `agent.md` files that can be corrected
|
|
390
|
+
* (e.g. headings that were folded by an earlier migration). Read-only; performs no writes and is
|
|
391
|
+
* intended for deciding whether to prompt the user before migrating.
|
|
392
|
+
*/
|
|
393
|
+
hasPendingCustomAgentMigration(): Promise<boolean>;
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Migrates every reachable `customAgents.yml` to the per-agent `agents/<id>/agent.md` layout and
|
|
397
|
+
* corrects already-migrated `agent.md` files whose headings were folded by an earlier migration.
|
|
389
398
|
* The user's original content is never deleted: on success (or on partial failure when no backup
|
|
390
399
|
* exists yet) the YAML is renamed to `customAgents.yml.bak`; if a `.bak` already exists it is not
|
|
391
|
-
* overwritten and the YAML is left in place.
|
|
392
|
-
*
|
|
400
|
+
* overwritten and the YAML is left in place. Corrections only overwrite `agent.md` files the user
|
|
401
|
+
* has not edited since migration.
|
|
402
|
+
* Idempotent — rerunning never overwrites an already up-to-date agent file.
|
|
393
403
|
*/
|
|
394
404
|
migrateCustomAgentsYaml(): Promise<Array<{
|
|
395
405
|
scope: URI;
|
|
@@ -399,6 +409,7 @@ export interface PromptFragmentCustomizationService {
|
|
|
399
409
|
failed: number;
|
|
400
410
|
yamlBackedUp: boolean;
|
|
401
411
|
promptOverridesMigrated: number;
|
|
412
|
+
corrected: number;
|
|
402
413
|
}>>;
|
|
403
414
|
|
|
404
415
|
/**
|