@octocodeai/freellama-mcp-server 0.9.0 → 0.10.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.
@@ -1690,12 +1690,18 @@ function evidencePaths(observations, workspace, limit = 10) {
1690
1690
  }
1691
1691
  return found;
1692
1692
  }
1693
- function groundingRepairNotice(missing, evidence) {
1694
- return `These paths in your answer do not exist in the workspace: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + "Return a finish action again that cites only paths that exist; if the evidence does not establish the answer, say so.";
1693
+ function unseenPaths(answer, observations) {
1694
+ return pathsIn(answer).filter((candidate) => !observations.some((text) => text.includes(candidate)));
1695
1695
  }
1696
- function groundingNote(missing) {
1696
+ function observedPaths(observations, limit = 10) {
1697
+ return [...new Set(observations.flatMap((text) => pathsIn(text)))].slice(0, limit);
1698
+ }
1699
+ function groundingRepairNotice(missing, evidence, observed = false) {
1700
+ return `These paths in your answer ${observed ? "did not appear in any tool result" : "do not exist in the workspace"}: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + `Return a finish action again that cites only paths ${observed ? "your tool results showed" : "that exist"}; if the evidence does not establish the answer, say so.`;
1701
+ }
1702
+ function groundingNote(missing, observed = false) {
1697
1703
  return `
1698
- [freellama: cited path(s) not found in the workspace: ${missing.join(", ")}; treat this answer as unverified]`;
1704
+ [freellama: cited path(s) ${observed ? "not seen in any tool result" : "not found in the workspace"}: ${missing.join(", ")}; treat this answer as unverified]`;
1699
1705
  }
1700
1706
  function toolErrorHint(message) {
1701
1707
  if (/rg: unrecognized flag --(?:exclude-dir|include)/.test(message)) {
@@ -1886,6 +1892,9 @@ async function runLoop(spec, hooks, env, inputs) {
1886
1892
  let groundingRepaired = false;
1887
1893
  let sourceRepaired = false;
1888
1894
  const seenOutputs = [];
1895
+ const observed = spec.grounding === "observed";
1896
+ const ungrounded = (text) => observed ? unseenPaths(text, seenOutputs) : missingPaths(text, root);
1897
+ const evidenceFor = () => observed ? observedPaths(seenOutputs) : evidencePaths(seenOutputs, root);
1889
1898
  for (let turn = 0; turn < runtime.max_turns; turn += 1) {
1890
1899
  const at = { turn: turn + 1, maxTurns: runtime.max_turns };
1891
1900
  phaseAt = at;
@@ -1937,20 +1946,20 @@ async function runLoop(spec, hooks, env, inputs) {
1937
1946
  }
1938
1947
  if (action.action === "finish") {
1939
1948
  const proposed = trimSpace(String(action.answer ?? ""));
1940
- const missing = missingPaths(proposed, root);
1949
+ const missing = ungrounded(proposed);
1941
1950
  if (missing.length > 0 && !groundingRepaired) {
1942
1951
  groundingRepaired = true;
1943
1952
  progress?.({ event: "repair", ...at });
1944
1953
  messages.push({ role: "assistant", content: echo(action) });
1945
- messages.push({ role: "user", content: groundingRepairNotice(missing, evidencePaths(seenOutputs, root)) });
1954
+ messages.push({ role: "user", content: groundingRepairNotice(missing, evidenceFor(), observed) });
1946
1955
  if (!refit()) {
1947
- answer = proposed + groundingNote(missing);
1956
+ answer = proposed + groundingNote(missing, observed);
1948
1957
  exhausted = false;
1949
1958
  break;
1950
1959
  }
1951
1960
  continue;
1952
1961
  }
1953
- const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidencePaths(seenOutputs, root) : [];
1962
+ const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidenceFor() : [];
1954
1963
  if (evidence.length > 0) {
1955
1964
  sourceRepaired = true;
1956
1965
  progress?.({ event: "repair", ...at });
@@ -1964,7 +1973,7 @@ async function runLoop(spec, hooks, env, inputs) {
1964
1973
  continue;
1965
1974
  }
1966
1975
  progress?.({ event: "finish", ...at });
1967
- answer = missing.length > 0 ? proposed + groundingNote(missing) : proposed;
1976
+ answer = missing.length > 0 ? proposed + groundingNote(missing, observed) : proposed;
1968
1977
  exhausted = false;
1969
1978
  break;
1970
1979
  }
@@ -2020,8 +2029,8 @@ async function runLoop(spec, hooks, env, inputs) {
2020
2029
  const finalAction = spec.parseAction(content(response));
2021
2030
  if (finalAction.action !== "finish") throw new ValidationError("forced final response was not finish");
2022
2031
  answer = trimSpace(String(finalAction.answer ?? ""));
2023
- const missing = missingPaths(answer, root);
2024
- if (missing.length > 0) answer += groundingNote(missing);
2032
+ const missing = ungrounded(answer);
2033
+ if (missing.length > 0) answer += groundingNote(missing, observed);
2025
2034
  } catch (error) {
2026
2035
  if (!isTransportError(error) && !isValidationError(error)) throw error;
2027
2036
  let diagnostic;
@@ -2595,7 +2604,7 @@ Verify: local answers are candidates. Each run_task result ends with check: line
2595
2604
 
2596
2605
  Coding: the answer is SEARCH/REPLACE blocks. ${APPLY_EDITS}
2597
2606
 
2598
- Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file].
2607
+ Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file]. Answers over 8,000 characters come back as a summary with check: lines and an output_file holding the whole answer; output:"inline" returns the whole text, outputPath:"<path>" writes (or replaces) that file inside your project.
2599
2608
 
2600
2609
  Approval: pull a model only after the user approves one exact tag and its size (ollama_pull approvedSizeBytes). Unload only a model you warmed or the user named; delete only a tag the user names. A memory refusal names what holds memory: tell the user and never stop their processes.`;
2601
2610
 
@@ -1695,12 +1695,18 @@ function evidencePaths(observations, workspace, limit = 10) {
1695
1695
  }
1696
1696
  return found;
1697
1697
  }
1698
- function groundingRepairNotice(missing, evidence) {
1699
- return `These paths in your answer do not exist in the workspace: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + "Return a finish action again that cites only paths that exist; if the evidence does not establish the answer, say so.";
1698
+ function unseenPaths(answer, observations) {
1699
+ return pathsIn(answer).filter((candidate) => !observations.some((text) => text.includes(candidate)));
1700
1700
  }
1701
- function groundingNote(missing) {
1701
+ function observedPaths(observations, limit = 10) {
1702
+ return [...new Set(observations.flatMap((text) => pathsIn(text)))].slice(0, limit);
1703
+ }
1704
+ function groundingRepairNotice(missing, evidence, observed = false) {
1705
+ return `These paths in your answer ${observed ? "did not appear in any tool result" : "do not exist in the workspace"}: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + `Return a finish action again that cites only paths ${observed ? "your tool results showed" : "that exist"}; if the evidence does not establish the answer, say so.`;
1706
+ }
1707
+ function groundingNote(missing, observed = false) {
1702
1708
  return `
1703
- [freellama: cited path(s) not found in the workspace: ${missing.join(", ")}; treat this answer as unverified]`;
1709
+ [freellama: cited path(s) ${observed ? "not seen in any tool result" : "not found in the workspace"}: ${missing.join(", ")}; treat this answer as unverified]`;
1704
1710
  }
1705
1711
  function toolErrorHint(message) {
1706
1712
  if (/rg: unrecognized flag --(?:exclude-dir|include)/.test(message)) {
@@ -1891,6 +1897,9 @@ async function runLoop(spec, hooks, env, inputs) {
1891
1897
  let groundingRepaired = false;
1892
1898
  let sourceRepaired = false;
1893
1899
  const seenOutputs = [];
1900
+ const observed = spec.grounding === "observed";
1901
+ const ungrounded = (text) => observed ? unseenPaths(text, seenOutputs) : missingPaths(text, root);
1902
+ const evidenceFor = () => observed ? observedPaths(seenOutputs) : evidencePaths(seenOutputs, root);
1894
1903
  for (let turn = 0; turn < runtime.max_turns; turn += 1) {
1895
1904
  const at = { turn: turn + 1, maxTurns: runtime.max_turns };
1896
1905
  phaseAt = at;
@@ -1942,20 +1951,20 @@ async function runLoop(spec, hooks, env, inputs) {
1942
1951
  }
1943
1952
  if (action.action === "finish") {
1944
1953
  const proposed = trimSpace(String(action.answer ?? ""));
1945
- const missing = missingPaths(proposed, root);
1954
+ const missing = ungrounded(proposed);
1946
1955
  if (missing.length > 0 && !groundingRepaired) {
1947
1956
  groundingRepaired = true;
1948
1957
  progress?.({ event: "repair", ...at });
1949
1958
  messages.push({ role: "assistant", content: echo(action) });
1950
- messages.push({ role: "user", content: groundingRepairNotice(missing, evidencePaths(seenOutputs, root)) });
1959
+ messages.push({ role: "user", content: groundingRepairNotice(missing, evidenceFor(), observed) });
1951
1960
  if (!refit()) {
1952
- answer = proposed + groundingNote(missing);
1961
+ answer = proposed + groundingNote(missing, observed);
1953
1962
  exhausted = false;
1954
1963
  break;
1955
1964
  }
1956
1965
  continue;
1957
1966
  }
1958
- const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidencePaths(seenOutputs, root) : [];
1967
+ const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidenceFor() : [];
1959
1968
  if (evidence.length > 0) {
1960
1969
  sourceRepaired = true;
1961
1970
  progress?.({ event: "repair", ...at });
@@ -1969,7 +1978,7 @@ async function runLoop(spec, hooks, env, inputs) {
1969
1978
  continue;
1970
1979
  }
1971
1980
  progress?.({ event: "finish", ...at });
1972
- answer = missing.length > 0 ? proposed + groundingNote(missing) : proposed;
1981
+ answer = missing.length > 0 ? proposed + groundingNote(missing, observed) : proposed;
1973
1982
  exhausted = false;
1974
1983
  break;
1975
1984
  }
@@ -2025,8 +2034,8 @@ async function runLoop(spec, hooks, env, inputs) {
2025
2034
  const finalAction = spec.parseAction(content(response));
2026
2035
  if (finalAction.action !== "finish") throw new ValidationError("forced final response was not finish");
2027
2036
  answer = trimSpace(String(finalAction.answer ?? ""));
2028
- const missing = missingPaths(answer, root);
2029
- if (missing.length > 0) answer += groundingNote(missing);
2037
+ const missing = ungrounded(answer);
2038
+ if (missing.length > 0) answer += groundingNote(missing, observed);
2030
2039
  } catch (error) {
2031
2040
  if (!isTransportError(error) && !isValidationError(error)) throw error;
2032
2041
  let diagnostic;
@@ -4281,7 +4290,7 @@ Verify: local answers are candidates. Each run_task result ends with check: line
4281
4290
 
4282
4291
  Coding: the answer is SEARCH/REPLACE blocks. ${APPLY_EDITS}
4283
4292
 
4284
- Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file].
4293
+ Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file]. Answers over 8,000 characters come back as a summary with check: lines and an output_file holding the whole answer; output:"inline" returns the whole text, outputPath:"<path>" writes (or replaces) that file inside your project.
4285
4294
 
4286
4295
  Approval: pull a model only after the user approves one exact tag and its size (ollama_pull approvedSizeBytes). Unload only a model you warmed or the user named; delete only a tag the user names. A memory refusal names what holds memory: tell the user and never stop their processes.`;
4287
4296
 
@@ -1653,12 +1653,18 @@ function evidencePaths(observations, workspace, limit = 10) {
1653
1653
  }
1654
1654
  return found;
1655
1655
  }
1656
- function groundingRepairNotice(missing, evidence) {
1657
- return `These paths in your answer do not exist in the workspace: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + "Return a finish action again that cites only paths that exist; if the evidence does not establish the answer, say so.";
1656
+ function unseenPaths(answer, observations) {
1657
+ return pathsIn(answer).filter((candidate) => !observations.some((text) => text.includes(candidate)));
1658
1658
  }
1659
- function groundingNote(missing) {
1659
+ function observedPaths(observations, limit = 10) {
1660
+ return [...new Set(observations.flatMap((text) => pathsIn(text)))].slice(0, limit);
1661
+ }
1662
+ function groundingRepairNotice(missing, evidence, observed = false) {
1663
+ return `These paths in your answer ${observed ? "did not appear in any tool result" : "do not exist in the workspace"}: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + `Return a finish action again that cites only paths ${observed ? "your tool results showed" : "that exist"}; if the evidence does not establish the answer, say so.`;
1664
+ }
1665
+ function groundingNote(missing, observed = false) {
1660
1666
  return `
1661
- [freellama: cited path(s) not found in the workspace: ${missing.join(", ")}; treat this answer as unverified]`;
1667
+ [freellama: cited path(s) ${observed ? "not seen in any tool result" : "not found in the workspace"}: ${missing.join(", ")}; treat this answer as unverified]`;
1662
1668
  }
1663
1669
  function toolErrorHint(message) {
1664
1670
  if (/rg: unrecognized flag --(?:exclude-dir|include)/.test(message)) {
@@ -1849,6 +1855,9 @@ async function runLoop(spec, hooks, env, inputs) {
1849
1855
  let groundingRepaired = false;
1850
1856
  let sourceRepaired = false;
1851
1857
  const seenOutputs = [];
1858
+ const observed = spec.grounding === "observed";
1859
+ const ungrounded = (text) => observed ? unseenPaths(text, seenOutputs) : missingPaths(text, root);
1860
+ const evidenceFor = () => observed ? observedPaths(seenOutputs) : evidencePaths(seenOutputs, root);
1852
1861
  for (let turn = 0; turn < runtime.max_turns; turn += 1) {
1853
1862
  const at = { turn: turn + 1, maxTurns: runtime.max_turns };
1854
1863
  phaseAt = at;
@@ -1900,20 +1909,20 @@ async function runLoop(spec, hooks, env, inputs) {
1900
1909
  }
1901
1910
  if (action.action === "finish") {
1902
1911
  const proposed = trimSpace(String(action.answer ?? ""));
1903
- const missing = missingPaths(proposed, root);
1912
+ const missing = ungrounded(proposed);
1904
1913
  if (missing.length > 0 && !groundingRepaired) {
1905
1914
  groundingRepaired = true;
1906
1915
  progress?.({ event: "repair", ...at });
1907
1916
  messages.push({ role: "assistant", content: echo(action) });
1908
- messages.push({ role: "user", content: groundingRepairNotice(missing, evidencePaths(seenOutputs, root)) });
1917
+ messages.push({ role: "user", content: groundingRepairNotice(missing, evidenceFor(), observed) });
1909
1918
  if (!refit()) {
1910
- answer = proposed + groundingNote(missing);
1919
+ answer = proposed + groundingNote(missing, observed);
1911
1920
  exhausted = false;
1912
1921
  break;
1913
1922
  }
1914
1923
  continue;
1915
1924
  }
1916
- const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidencePaths(seenOutputs, root) : [];
1925
+ const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidenceFor() : [];
1917
1926
  if (evidence.length > 0) {
1918
1927
  sourceRepaired = true;
1919
1928
  progress?.({ event: "repair", ...at });
@@ -1927,7 +1936,7 @@ async function runLoop(spec, hooks, env, inputs) {
1927
1936
  continue;
1928
1937
  }
1929
1938
  progress?.({ event: "finish", ...at });
1930
- answer = missing.length > 0 ? proposed + groundingNote(missing) : proposed;
1939
+ answer = missing.length > 0 ? proposed + groundingNote(missing, observed) : proposed;
1931
1940
  exhausted = false;
1932
1941
  break;
1933
1942
  }
@@ -1983,8 +1992,8 @@ async function runLoop(spec, hooks, env, inputs) {
1983
1992
  const finalAction = spec.parseAction(content(response));
1984
1993
  if (finalAction.action !== "finish") throw new ValidationError("forced final response was not finish");
1985
1994
  answer = trimSpace(String(finalAction.answer ?? ""));
1986
- const missing = missingPaths(answer, root);
1987
- if (missing.length > 0) answer += groundingNote(missing);
1995
+ const missing = ungrounded(answer);
1996
+ if (missing.length > 0) answer += groundingNote(missing, observed);
1988
1997
  } catch (error) {
1989
1998
  if (!isTransportError(error) && !isValidationError(error)) throw error;
1990
1999
  let diagnostic;
@@ -2681,7 +2690,7 @@ Verify: local answers are candidates. Each run_task result ends with check: line
2681
2690
 
2682
2691
  Coding: the answer is SEARCH/REPLACE blocks. ${APPLY_EDITS}
2683
2692
 
2684
- Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file].
2693
+ Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file]. Answers over 8,000 characters come back as a summary with check: lines and an output_file holding the whole answer; output:"inline" returns the whole text, outputPath:"<path>" writes (or replaces) that file inside your project.
2685
2694
 
2686
2695
  Approval: pull a model only after the user approves one exact tag and its size (ollama_pull approvedSizeBytes). Unload only a model you warmed or the user named; delete only a tag the user names. A memory refusal names what holds memory: tell the user and never stop their processes.`;
2687
2696
 
@@ -2919,5 +2928,5 @@ if (isEntry(import.meta.url, "octocode-agent")) {
2919
2928
 
2920
2929
  // src/research/github-agent.ts
2921
2930
  if (isEntry(import.meta.url, "github-agent")) {
2922
- process.exitCode = await runAdapter(GITHUB_ADAPTER);
2931
+ process.exitCode = await runAdapter({ ...GITHUB_ADAPTER, grounding: "observed" });
2923
2932
  }
@@ -1658,12 +1658,18 @@ function evidencePaths(observations, workspace, limit = 10) {
1658
1658
  }
1659
1659
  return found;
1660
1660
  }
1661
- function groundingRepairNotice(missing, evidence) {
1662
- return `These paths in your answer do not exist in the workspace: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + "Return a finish action again that cites only paths that exist; if the evidence does not establish the answer, say so.";
1661
+ function unseenPaths(answer, observations) {
1662
+ return pathsIn(answer).filter((candidate) => !observations.some((text) => text.includes(candidate)));
1663
1663
  }
1664
- function groundingNote(missing) {
1664
+ function observedPaths(observations, limit = 10) {
1665
+ return [...new Set(observations.flatMap((text) => pathsIn(text)))].slice(0, limit);
1666
+ }
1667
+ function groundingRepairNotice(missing, evidence, observed = false) {
1668
+ return `These paths in your answer ${observed ? "did not appear in any tool result" : "do not exist in the workspace"}: ${missing.join(", ")}. ` + (evidence.length > 0 ? `Paths your own commands returned: ${evidence.join(", ")}. ` : "") + `Return a finish action again that cites only paths ${observed ? "your tool results showed" : "that exist"}; if the evidence does not establish the answer, say so.`;
1669
+ }
1670
+ function groundingNote(missing, observed = false) {
1665
1671
  return `
1666
- [freellama: cited path(s) not found in the workspace: ${missing.join(", ")}; treat this answer as unverified]`;
1672
+ [freellama: cited path(s) ${observed ? "not seen in any tool result" : "not found in the workspace"}: ${missing.join(", ")}; treat this answer as unverified]`;
1667
1673
  }
1668
1674
  function toolErrorHint(message) {
1669
1675
  if (/rg: unrecognized flag --(?:exclude-dir|include)/.test(message)) {
@@ -1854,6 +1860,9 @@ async function runLoop(spec, hooks, env, inputs) {
1854
1860
  let groundingRepaired = false;
1855
1861
  let sourceRepaired = false;
1856
1862
  const seenOutputs = [];
1863
+ const observed = spec.grounding === "observed";
1864
+ const ungrounded = (text) => observed ? unseenPaths(text, seenOutputs) : missingPaths(text, root);
1865
+ const evidenceFor = () => observed ? observedPaths(seenOutputs) : evidencePaths(seenOutputs, root);
1857
1866
  for (let turn = 0; turn < runtime.max_turns; turn += 1) {
1858
1867
  const at = { turn: turn + 1, maxTurns: runtime.max_turns };
1859
1868
  phaseAt = at;
@@ -1905,20 +1914,20 @@ async function runLoop(spec, hooks, env, inputs) {
1905
1914
  }
1906
1915
  if (action.action === "finish") {
1907
1916
  const proposed = trimSpace(String(action.answer ?? ""));
1908
- const missing = missingPaths(proposed, root);
1917
+ const missing = ungrounded(proposed);
1909
1918
  if (missing.length > 0 && !groundingRepaired) {
1910
1919
  groundingRepaired = true;
1911
1920
  progress?.({ event: "repair", ...at });
1912
1921
  messages.push({ role: "assistant", content: echo(action) });
1913
- messages.push({ role: "user", content: groundingRepairNotice(missing, evidencePaths(seenOutputs, root)) });
1922
+ messages.push({ role: "user", content: groundingRepairNotice(missing, evidenceFor(), observed) });
1914
1923
  if (!refit()) {
1915
- answer = proposed + groundingNote(missing);
1924
+ answer = proposed + groundingNote(missing, observed);
1916
1925
  exhausted = false;
1917
1926
  break;
1918
1927
  }
1919
1928
  continue;
1920
1929
  }
1921
- const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidencePaths(seenOutputs, root) : [];
1930
+ const evidence = spec.requireSource !== false && missing.length === 0 && !sourceRepaired && pathsIn(proposed).length === 0 ? evidenceFor() : [];
1922
1931
  if (evidence.length > 0) {
1923
1932
  sourceRepaired = true;
1924
1933
  progress?.({ event: "repair", ...at });
@@ -1932,7 +1941,7 @@ async function runLoop(spec, hooks, env, inputs) {
1932
1941
  continue;
1933
1942
  }
1934
1943
  progress?.({ event: "finish", ...at });
1935
- answer = missing.length > 0 ? proposed + groundingNote(missing) : proposed;
1944
+ answer = missing.length > 0 ? proposed + groundingNote(missing, observed) : proposed;
1936
1945
  exhausted = false;
1937
1946
  break;
1938
1947
  }
@@ -1988,8 +1997,8 @@ async function runLoop(spec, hooks, env, inputs) {
1988
1997
  const finalAction = spec.parseAction(content(response));
1989
1998
  if (finalAction.action !== "finish") throw new ValidationError("forced final response was not finish");
1990
1999
  answer = trimSpace(String(finalAction.answer ?? ""));
1991
- const missing = missingPaths(answer, root);
1992
- if (missing.length > 0) answer += groundingNote(missing);
2000
+ const missing = ungrounded(answer);
2001
+ if (missing.length > 0) answer += groundingNote(missing, observed);
1993
2002
  } catch (error) {
1994
2003
  if (!isTransportError(error) && !isValidationError(error)) throw error;
1995
2004
  let diagnostic;
@@ -2681,7 +2690,7 @@ Verify: local answers are candidates. Each run_task result ends with check: line
2681
2690
 
2682
2691
  Coding: the answer is SEARCH/REPLACE blocks. ${APPLY_EDITS}
2683
2692
 
2684
- Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file].
2693
+ Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file]. Answers over 8,000 characters come back as a summary with check: lines and an output_file holding the whole answer; output:"inline" returns the whole text, outputPath:"<path>" writes (or replaces) that file inside your project.
2685
2694
 
2686
2695
  Approval: pull a model only after the user approves one exact tag and its size (ollama_pull approvedSizeBytes). Unload only a model you warmed or the user named; delete only a tag the user names. A memory refusal names what holds memory: tell the user and never stop their processes.`;
2687
2696
 
@@ -192,7 +192,7 @@ command reports the core catalog and does not expose these enrichment flags.
192
192
  |---|---|---|---|
193
193
  | `includeLibrary` | `installed`, `detail` | `false` | Requests public metadata; the default makes no website calls |
194
194
  | `includeReadme` | Library step 2, or `detail` with `includeLibrary:true` | `false` | Returns the README text with its format |
195
- | `includeVerbose` | `detail` | `false` | Returns Ollama's license and Modelfile fields independently of README enrichment |
195
+ | `includeVerbose` | `installed`, `resident`, `detail` | `false` | `installed`/`resident`: full per-model records (benchmark, policy rank, execution observation, thinking, context defaults) instead of the slim set; `detail`: Ollama's license and Modelfile fields, independently of README enrichment |
196
196
  | `limit`, `cursor` | `raw`, library tag listings; `limit` also applies to library search | Tag/raw page size 20; search limit 10; maximum 50 | Pages large lists; the tag cursor rejects a changed tag list |
197
197
  | `query`, `capabilities`, `order`, `source` | Library search | Popular order, both sources | Filter by capability word (such as `decision`), order by `popular` or `newest`, or limit to `ollama` or `huggingface` |
198
198
  | `ollamaEndpoint` | Direct Ollama views and library inventory reads | Configured Ollama endpoint | Chooses the Ollama instance; enriched managed inventory uses each model's assigned backend |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octocodeai/freellama-mcp-server",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "MCP server exposing FreeLlama core as tools, via native NAPI bindings into packages/rust-core.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -50,11 +50,11 @@
50
50
  "zod": "^4.6.7"
51
51
  },
52
52
  "optionalDependencies": {
53
- "@octocodeai/freellama-native-darwin-arm64": "0.9.0",
54
- "@octocodeai/freellama-native-linux-arm64-gnu": "0.9.0",
55
- "@octocodeai/freellama-native-linux-x64-gnu": "0.9.0",
56
- "@octocodeai/freellama-native-win32-arm64-msvc": "0.9.0",
57
- "@octocodeai/freellama-native-win32-x64-msvc": "0.9.0"
53
+ "@octocodeai/freellama-native-darwin-arm64": "0.10.0",
54
+ "@octocodeai/freellama-native-linux-arm64-gnu": "0.10.0",
55
+ "@octocodeai/freellama-native-linux-x64-gnu": "0.10.0",
56
+ "@octocodeai/freellama-native-win32-arm64-msvc": "0.10.0",
57
+ "@octocodeai/freellama-native-win32-x64-msvc": "0.10.0"
58
58
  },
59
59
  "author": "Guy Bary",
60
60
  "license": "Apache-2.0 OR MIT",