codemini-cli 0.2.2 → 0.2.4
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/package.json +1 -1
- package/skills/superpowers-lite/SKILL.md +20 -6
- package/src/cli.js +1 -1
- package/src/commands/run.js +3 -1
- package/src/core/agent-loop.js +327 -68
- package/src/core/chat-runtime.js +336 -104
- package/src/core/context-compact.js +32 -2
- package/src/core/default-system-prompt.js +22 -1
- package/src/core/session-store.js +19 -0
- package/src/core/shell-profile.js +47 -1
- package/src/core/tools.js +323 -82
package/src/core/tools.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { evaluateCommandPolicy } from './command-policy.js';
|
|
16
16
|
import { queryAst, readAstNode, resolveAstTarget } from './ast.js';
|
|
17
17
|
import { initializeProjectIndex, refreshIndexedFile } from './project-index.js';
|
|
18
|
+
import { checkReadDedup } from './agent-loop.js';
|
|
18
19
|
|
|
19
20
|
const SKIP_DIRS = new Set(['.git', 'node_modules', '.codemini', '.codemini-global', 'dist', 'coverage']);
|
|
20
21
|
const TEXT_EXTENSIONS = new Set([
|
|
@@ -722,6 +723,26 @@ async function readFile(root, args) {
|
|
|
722
723
|
truncated = true;
|
|
723
724
|
}
|
|
724
725
|
|
|
726
|
+
// Read deduplication: if same path+range+mtime was read before, return a short stub
|
|
727
|
+
const isDuplicate = checkReadDedup(
|
|
728
|
+
args?.path,
|
|
729
|
+
startLine,
|
|
730
|
+
endLine,
|
|
731
|
+
stat.mtimeMs
|
|
732
|
+
);
|
|
733
|
+
if (isDuplicate) {
|
|
734
|
+
return {
|
|
735
|
+
path: args?.path,
|
|
736
|
+
phase: 'content',
|
|
737
|
+
start_line: startLine,
|
|
738
|
+
end_line: endLine,
|
|
739
|
+
total_lines: totalLines,
|
|
740
|
+
truncated: false,
|
|
741
|
+
unchanged: true,
|
|
742
|
+
content: `File unchanged since last read. The content from the earlier read tool_result in this conversation is still current -- refer to that instead of re-reading.`
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
725
746
|
return {
|
|
726
747
|
path: args?.path,
|
|
727
748
|
phase: 'content',
|
|
@@ -1711,22 +1732,22 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1711
1732
|
return null;
|
|
1712
1733
|
}
|
|
1713
1734
|
};
|
|
1714
|
-
const
|
|
1735
|
+
const primaryDefinitions = [
|
|
1715
1736
|
{
|
|
1716
1737
|
type: 'function',
|
|
1717
1738
|
function: {
|
|
1718
1739
|
name: 'read',
|
|
1719
1740
|
description:
|
|
1720
|
-
'
|
|
1741
|
+
'Inspect a file. Call once for metadata and a read_token, then again with include_content=true and the same token to get content. Use this before editing. Do not use run with cat, head, or tail for file reads.',
|
|
1721
1742
|
parameters: {
|
|
1722
1743
|
type: 'object',
|
|
1723
1744
|
properties: {
|
|
1724
|
-
path: { type: 'string' },
|
|
1725
|
-
start_line: { type: 'number' },
|
|
1726
|
-
end_line: { type: 'number' },
|
|
1727
|
-
max_chars: { type: 'number' },
|
|
1728
|
-
include_content: { type: 'boolean' },
|
|
1729
|
-
read_token: { type: 'string' }
|
|
1745
|
+
path: { type: 'string', description: 'File path to read' },
|
|
1746
|
+
start_line: { type: 'number', description: '1-based start line' },
|
|
1747
|
+
end_line: { type: 'number', description: 'Inclusive end line' },
|
|
1748
|
+
max_chars: { type: 'number', description: 'Max chars to return' },
|
|
1749
|
+
include_content: { type: 'boolean', description: 'Set true on the second call' },
|
|
1750
|
+
read_token: { type: 'string', description: 'Token from the first call' }
|
|
1730
1751
|
},
|
|
1731
1752
|
required: ['path']
|
|
1732
1753
|
}
|
|
@@ -1736,18 +1757,19 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1736
1757
|
type: 'function',
|
|
1737
1758
|
function: {
|
|
1738
1759
|
name: 'grep',
|
|
1739
|
-
description:
|
|
1760
|
+
description:
|
|
1761
|
+
'Search file contents. Use this for code search before read or edit. Do not use run with grep or rg for normal code search.',
|
|
1740
1762
|
parameters: {
|
|
1741
1763
|
type: 'object',
|
|
1742
1764
|
properties: {
|
|
1743
|
-
pattern: { type: 'string' },
|
|
1744
|
-
query: { type: 'string' },
|
|
1745
|
-
path: { type: 'string' },
|
|
1746
|
-
regex: { type: 'boolean' },
|
|
1747
|
-
case_sensitive: { type: 'boolean' },
|
|
1748
|
-
max_results: { type: 'number' },
|
|
1749
|
-
language: { type: 'string' },
|
|
1750
|
-
file_types: { type: 'array', items: { type: 'string' } }
|
|
1765
|
+
pattern: { type: 'string', description: 'Search pattern' },
|
|
1766
|
+
query: { type: 'string', description: 'Alias for pattern' },
|
|
1767
|
+
path: { type: 'string', description: 'Directory or file to search' },
|
|
1768
|
+
regex: { type: 'boolean', description: 'Treat pattern as regex' },
|
|
1769
|
+
case_sensitive: { type: 'boolean', description: 'Case-sensitive matching' },
|
|
1770
|
+
max_results: { type: 'number', description: 'Max matches to return' },
|
|
1771
|
+
language: { type: 'string', description: 'Filter by language' },
|
|
1772
|
+
file_types: { type: 'array', items: { type: 'string' }, description: 'Filter by file glob' }
|
|
1751
1773
|
},
|
|
1752
1774
|
required: ['pattern']
|
|
1753
1775
|
}
|
|
@@ -1757,14 +1779,15 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1757
1779
|
type: 'function',
|
|
1758
1780
|
function: {
|
|
1759
1781
|
name: 'glob',
|
|
1760
|
-
description:
|
|
1782
|
+
description:
|
|
1783
|
+
'Find files by glob pattern. Use this for file discovery before read. Do not use run with find for normal file lookup.',
|
|
1761
1784
|
parameters: {
|
|
1762
1785
|
type: 'object',
|
|
1763
1786
|
properties: {
|
|
1764
|
-
pattern: { type: 'string' },
|
|
1765
|
-
path: { type: 'string' },
|
|
1766
|
-
include_hidden: { type: 'boolean' },
|
|
1767
|
-
max_results: { type: 'number' }
|
|
1787
|
+
pattern: { type: 'string', description: 'Glob pattern' },
|
|
1788
|
+
path: { type: 'string', description: 'Directory to search' },
|
|
1789
|
+
include_hidden: { type: 'boolean', description: 'Include dotfiles' },
|
|
1790
|
+
max_results: { type: 'number', description: 'Max results' }
|
|
1768
1791
|
},
|
|
1769
1792
|
required: ['pattern']
|
|
1770
1793
|
}
|
|
@@ -1774,12 +1797,12 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1774
1797
|
type: 'function',
|
|
1775
1798
|
function: {
|
|
1776
1799
|
name: 'list',
|
|
1777
|
-
description: 'List files and directories in a workspace path',
|
|
1800
|
+
description: 'List files and directories in a workspace path. Use this for quick directory discovery before deeper reads.',
|
|
1778
1801
|
parameters: {
|
|
1779
1802
|
type: 'object',
|
|
1780
1803
|
properties: {
|
|
1781
|
-
path: { type: 'string' },
|
|
1782
|
-
include_hidden: { type: 'boolean' }
|
|
1804
|
+
path: { type: 'string', description: 'Directory path to list' },
|
|
1805
|
+
include_hidden: { type: 'boolean', description: 'Include dotfiles' }
|
|
1783
1806
|
}
|
|
1784
1807
|
}
|
|
1785
1808
|
}
|
|
@@ -1789,24 +1812,24 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1789
1812
|
function: {
|
|
1790
1813
|
name: 'edit',
|
|
1791
1814
|
description:
|
|
1792
|
-
'
|
|
1815
|
+
'Edit existing files. Use block edits, exact replacements, or anchored inserts. When ast_target is provided, keep the edit constrained to that node. Read first unless the exact target is already known. Prefer this over write for code changes.',
|
|
1793
1816
|
parameters: {
|
|
1794
1817
|
type: 'object',
|
|
1795
1818
|
properties: {
|
|
1796
|
-
file: { type: 'string' },
|
|
1797
|
-
path: { type: 'string' },
|
|
1798
|
-
new_content: { type: 'string' },
|
|
1799
|
-
old_text: { type: 'string' },
|
|
1800
|
-
new_text: { type: 'string' },
|
|
1801
|
-
anchor_text: { type: 'string' },
|
|
1802
|
-
content: { type: 'string' },
|
|
1803
|
-
position: { type: 'string' },
|
|
1804
|
-
kind: { type: 'string' },
|
|
1805
|
-
target: { type: 'object' },
|
|
1806
|
-
ast_target: { type: 'object' },
|
|
1807
|
-
symbol: { type: 'string' },
|
|
1808
|
-
line: { type: 'number' },
|
|
1809
|
-
edit: { type: 'object' }
|
|
1819
|
+
file: { type: 'string', description: 'File path to edit' },
|
|
1820
|
+
path: { type: 'string', description: 'Alias for file' },
|
|
1821
|
+
new_content: { type: 'string', description: 'Replacement content' },
|
|
1822
|
+
old_text: { type: 'string', description: 'Exact text to replace' },
|
|
1823
|
+
new_text: { type: 'string', description: 'Replacement text' },
|
|
1824
|
+
anchor_text: { type: 'string', description: 'Anchor text for inserts' },
|
|
1825
|
+
content: { type: 'string', description: 'Content to insert or append' },
|
|
1826
|
+
position: { type: 'string', description: 'before or after' },
|
|
1827
|
+
kind: { type: 'string', description: 'replace_block, replace_text, insert_before, insert_after, or rewrite_file' },
|
|
1828
|
+
target: { type: 'object', description: 'Location object with symbol or line info' },
|
|
1829
|
+
ast_target: { type: 'object', description: 'AST target from ast_query' },
|
|
1830
|
+
symbol: { type: 'string', description: 'Symbol to target' },
|
|
1831
|
+
line: { type: 'number', description: 'Line to target' },
|
|
1832
|
+
edit: { type: 'object', description: 'Structured edit input' }
|
|
1810
1833
|
},
|
|
1811
1834
|
required: ['file']
|
|
1812
1835
|
}
|
|
@@ -1815,77 +1838,96 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1815
1838
|
{
|
|
1816
1839
|
type: 'function',
|
|
1817
1840
|
function: {
|
|
1818
|
-
name: '
|
|
1841
|
+
name: 'write',
|
|
1819
1842
|
description:
|
|
1820
|
-
'
|
|
1843
|
+
'Create a new file or overwrite a file. Use this for new files or explicit full rewrites. Prefer edit for existing code changes.',
|
|
1821
1844
|
parameters: {
|
|
1822
1845
|
type: 'object',
|
|
1823
1846
|
properties: {
|
|
1824
|
-
path: { type: 'string' },
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
max_results: { type: 'number' }
|
|
1847
|
+
path: { type: 'string', description: 'File path to create or overwrite' },
|
|
1848
|
+
content: { type: 'string', description: 'Content to write' },
|
|
1849
|
+
append: { type: 'boolean', description: 'Append instead of overwrite' },
|
|
1850
|
+
full_file_rewrite: { type: 'boolean', description: 'Set true for whole-file rewrites' }
|
|
1829
1851
|
},
|
|
1830
|
-
required: ['path', '
|
|
1852
|
+
required: ['path', 'content']
|
|
1831
1853
|
}
|
|
1832
1854
|
}
|
|
1833
1855
|
},
|
|
1834
1856
|
{
|
|
1835
1857
|
type: 'function',
|
|
1836
1858
|
function: {
|
|
1837
|
-
name: '
|
|
1859
|
+
name: 'run',
|
|
1838
1860
|
description:
|
|
1839
|
-
'
|
|
1861
|
+
'Run a one-shot shell command such as install, build, or test. Do not use for long-running services or file search.',
|
|
1840
1862
|
parameters: {
|
|
1841
1863
|
type: 'object',
|
|
1842
1864
|
properties: {
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
ast_target: { type: 'object' }
|
|
1865
|
+
command: { type: 'string', description: 'Shell command to execute' },
|
|
1866
|
+
timeout: { type: 'number', description: 'Timeout in milliseconds' }
|
|
1846
1867
|
},
|
|
1847
|
-
required: ['
|
|
1868
|
+
required: ['command']
|
|
1848
1869
|
}
|
|
1849
1870
|
}
|
|
1850
1871
|
},
|
|
1851
1872
|
{
|
|
1852
1873
|
type: 'function',
|
|
1853
1874
|
function: {
|
|
1854
|
-
name: '
|
|
1875
|
+
name: 'tool_search',
|
|
1876
|
+
description:
|
|
1877
|
+
'Load one deferred tool schema by name. Use this when a needed tool is not in the current tool list.',
|
|
1878
|
+
parameters: {
|
|
1879
|
+
type: 'object',
|
|
1880
|
+
properties: {
|
|
1881
|
+
query: { type: 'string', description: 'Tool name to load, or "all"' }
|
|
1882
|
+
},
|
|
1883
|
+
required: ['query']
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
];
|
|
1888
|
+
|
|
1889
|
+
const deferredDefinitions = {
|
|
1890
|
+
ast_query: {
|
|
1891
|
+
type: 'function',
|
|
1892
|
+
function: {
|
|
1893
|
+
name: 'ast_query',
|
|
1855
1894
|
description:
|
|
1856
|
-
'
|
|
1895
|
+
'Run a Tree-sitter query on a code file and return ast_target objects. Use this when you need node-scoped reads or edits for functions, classes, or methods.',
|
|
1857
1896
|
parameters: {
|
|
1858
1897
|
type: 'object',
|
|
1859
1898
|
properties: {
|
|
1860
1899
|
path: { type: 'string' },
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1900
|
+
language: { type: 'string' },
|
|
1901
|
+
query: { type: 'string' },
|
|
1902
|
+
capture_name: { type: 'string' },
|
|
1903
|
+
max_results: { type: 'number' }
|
|
1864
1904
|
},
|
|
1865
|
-
required: ['path', '
|
|
1905
|
+
required: ['path', 'query']
|
|
1866
1906
|
}
|
|
1867
1907
|
}
|
|
1868
1908
|
},
|
|
1869
|
-
{
|
|
1909
|
+
read_ast_node: {
|
|
1870
1910
|
type: 'function',
|
|
1871
1911
|
function: {
|
|
1872
|
-
name: '
|
|
1912
|
+
name: 'read_ast_node',
|
|
1873
1913
|
description:
|
|
1874
|
-
'
|
|
1914
|
+
'Read a previously selected AST node with compact structural context. Use this after ast_query before a scoped structural edit.',
|
|
1875
1915
|
parameters: {
|
|
1876
1916
|
type: 'object',
|
|
1877
1917
|
properties: {
|
|
1878
|
-
|
|
1918
|
+
path: { type: 'string' },
|
|
1919
|
+
language: { type: 'string' },
|
|
1920
|
+
ast_target: { type: 'object' }
|
|
1879
1921
|
},
|
|
1880
|
-
required: ['
|
|
1922
|
+
required: ['path', 'ast_target']
|
|
1881
1923
|
}
|
|
1882
1924
|
}
|
|
1883
1925
|
},
|
|
1884
|
-
{
|
|
1926
|
+
generate_diff: {
|
|
1885
1927
|
type: 'function',
|
|
1886
1928
|
function: {
|
|
1887
1929
|
name: 'generate_diff',
|
|
1888
|
-
description: 'Generate a unified diff
|
|
1930
|
+
description: 'Generate a unified diff for proposed content. Use this when you want to preview or prepare a patch before applying it.',
|
|
1889
1931
|
parameters: {
|
|
1890
1932
|
type: 'object',
|
|
1891
1933
|
properties: {
|
|
@@ -1896,11 +1938,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1896
1938
|
}
|
|
1897
1939
|
}
|
|
1898
1940
|
},
|
|
1899
|
-
{
|
|
1941
|
+
patch: {
|
|
1900
1942
|
type: 'function',
|
|
1901
1943
|
function: {
|
|
1902
1944
|
name: 'patch',
|
|
1903
|
-
description: 'Apply one or more unified diff hunks to files
|
|
1945
|
+
description: 'Apply one or more unified diff hunks to workspace files. Use this for prepared unified diffs instead of ad-hoc shell patching.',
|
|
1904
1946
|
parameters: {
|
|
1905
1947
|
type: 'object',
|
|
1906
1948
|
properties: {
|
|
@@ -1911,12 +1953,12 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1911
1953
|
}
|
|
1912
1954
|
}
|
|
1913
1955
|
},
|
|
1914
|
-
{
|
|
1956
|
+
start_service: {
|
|
1915
1957
|
type: 'function',
|
|
1916
1958
|
function: {
|
|
1917
1959
|
name: 'start_service',
|
|
1918
1960
|
description:
|
|
1919
|
-
'Start a long-running local service
|
|
1961
|
+
'Start a long-running local service and return a compact handle. Do not use run for watchers, dev servers, or other persistent processes.',
|
|
1920
1962
|
parameters: {
|
|
1921
1963
|
type: 'object',
|
|
1922
1964
|
properties: {
|
|
@@ -1939,22 +1981,22 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1939
1981
|
}
|
|
1940
1982
|
}
|
|
1941
1983
|
},
|
|
1942
|
-
{
|
|
1984
|
+
list_services: {
|
|
1943
1985
|
type: 'function',
|
|
1944
1986
|
function: {
|
|
1945
1987
|
name: 'list_services',
|
|
1946
|
-
description: 'List
|
|
1988
|
+
description: 'List tracked local services and their current status. Use this to find existing service handles before starting another one.',
|
|
1947
1989
|
parameters: {
|
|
1948
1990
|
type: 'object',
|
|
1949
1991
|
properties: {}
|
|
1950
1992
|
}
|
|
1951
1993
|
}
|
|
1952
1994
|
},
|
|
1953
|
-
{
|
|
1995
|
+
get_service_status: {
|
|
1954
1996
|
type: 'function',
|
|
1955
1997
|
function: {
|
|
1956
1998
|
name: 'get_service_status',
|
|
1957
|
-
description: 'Get the
|
|
1999
|
+
description: 'Get the status of a started service. Use this to confirm startup or diagnose a stalled service.',
|
|
1958
2000
|
parameters: {
|
|
1959
2001
|
type: 'object',
|
|
1960
2002
|
properties: {
|
|
@@ -1964,11 +2006,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1964
2006
|
}
|
|
1965
2007
|
}
|
|
1966
2008
|
},
|
|
1967
|
-
{
|
|
2009
|
+
get_service_logs: {
|
|
1968
2010
|
type: 'function',
|
|
1969
2011
|
function: {
|
|
1970
2012
|
name: 'get_service_logs',
|
|
1971
|
-
description: 'Read recent logs from a
|
|
2013
|
+
description: 'Read recent logs from a started service. Use this for targeted diagnosis instead of restarting blindly.',
|
|
1972
2014
|
parameters: {
|
|
1973
2015
|
type: 'object',
|
|
1974
2016
|
properties: {
|
|
@@ -1980,11 +2022,11 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1980
2022
|
}
|
|
1981
2023
|
}
|
|
1982
2024
|
},
|
|
1983
|
-
{
|
|
2025
|
+
stop_service: {
|
|
1984
2026
|
type: 'function',
|
|
1985
2027
|
function: {
|
|
1986
2028
|
name: 'stop_service',
|
|
1987
|
-
description: 'Stop a
|
|
2029
|
+
description: 'Stop a started service when it is no longer needed or when you need a clean restart.',
|
|
1988
2030
|
parameters: {
|
|
1989
2031
|
type: 'object',
|
|
1990
2032
|
properties: {
|
|
@@ -1994,7 +2036,9 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
1994
2036
|
}
|
|
1995
2037
|
}
|
|
1996
2038
|
}
|
|
1997
|
-
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
const definitions = [...primaryDefinitions];
|
|
1998
2042
|
|
|
1999
2043
|
const handlers = {
|
|
2000
2044
|
read: (args) =>
|
|
@@ -2052,8 +2096,205 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSyste
|
|
|
2052
2096
|
list_services: () => listServices(workspaceRoot),
|
|
2053
2097
|
get_service_status: (args) => getServiceStatus(workspaceRoot, args),
|
|
2054
2098
|
get_service_logs: (args) => getServiceLogs(workspaceRoot, args),
|
|
2055
|
-
stop_service: (args) => stopService(workspaceRoot, args)
|
|
2099
|
+
stop_service: (args) => stopService(workspaceRoot, args),
|
|
2100
|
+
tool_search: (args) => {
|
|
2101
|
+
const query = String(args?.query || '').trim().toLowerCase();
|
|
2102
|
+
if (query === 'all') {
|
|
2103
|
+
const all = Object.values(deferredDefinitions);
|
|
2104
|
+
return {
|
|
2105
|
+
loaded: Object.keys(deferredDefinitions),
|
|
2106
|
+
schemas: all,
|
|
2107
|
+
message: `Loaded all ${all.length} deferred tools. You can now call them directly.`
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
const match = Object.entries(deferredDefinitions).find(([name]) => name === query);
|
|
2111
|
+
if (!match) {
|
|
2112
|
+
const available = Object.keys(deferredDefinitions).join(', ');
|
|
2113
|
+
return { error: `Unknown tool: "${query}". Available deferred tools: ${available}` };
|
|
2114
|
+
}
|
|
2115
|
+
return {
|
|
2116
|
+
loaded: [match[0]],
|
|
2117
|
+
schemas: [match[1]],
|
|
2118
|
+
message: `Loaded tool "${match[0]}". You can now call it in your next response.`
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
|
|
2123
|
+
const formatters = {
|
|
2124
|
+
read(result) {
|
|
2125
|
+
if (typeof result === 'string') return result;
|
|
2126
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2127
|
+
// Phase 1 metadata: small, return as-is
|
|
2128
|
+
if (result.phase === 'metadata') {
|
|
2129
|
+
return JSON.stringify(result);
|
|
2130
|
+
}
|
|
2131
|
+
// Phase 2 content: structured header + head/tail content
|
|
2132
|
+
if (result.phase === 'content') {
|
|
2133
|
+
const header = `[File: ${result.path}, lines ${result.start_line || 1}-${result.end_line || '?'}${result.total_lines ? ` of ${result.total_lines}` : ''}${result.truncated ? ', truncated' : ''}]`;
|
|
2134
|
+
const content = result.content || '';
|
|
2135
|
+
if (typeof content !== 'string' || content.length <= 3000) {
|
|
2136
|
+
return `${header}\n${content}`;
|
|
2137
|
+
}
|
|
2138
|
+
const headLen = 1800;
|
|
2139
|
+
const tailLen = 800;
|
|
2140
|
+
return `${header}\n${content.slice(0, headLen)}\n... [omitted ${content.length - headLen - tailLen} chars] ...\n${content.slice(-tailLen)}`;
|
|
2141
|
+
}
|
|
2142
|
+
return JSON.stringify(result);
|
|
2143
|
+
},
|
|
2144
|
+
|
|
2145
|
+
grep(result) {
|
|
2146
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2147
|
+
const { pattern, matches, truncated } = result;
|
|
2148
|
+
const header = pattern ? `[grep: "${pattern}"]` : '';
|
|
2149
|
+
if (!Array.isArray(matches) || matches.length === 0) return `${header}\nNo matches found.`;
|
|
2150
|
+
if (matches.length <= 30) {
|
|
2151
|
+
const lines = matches.map((m) => `${m.path}:${m.line}: ${String(m.preview || '').slice(0, 120)}`);
|
|
2152
|
+
return `${header}\n${lines.join('\n')}`;
|
|
2153
|
+
}
|
|
2154
|
+
const shown = matches.slice(0, 30).map((m) => `${m.path}:${m.line}: ${String(m.preview || '').slice(0, 120)}`);
|
|
2155
|
+
return `${header}\n${shown.join('\n')}\n... and ${matches.length - 30} more matches [total: ${matches.length}${truncated ? ', results were truncated' : ''}]`;
|
|
2156
|
+
},
|
|
2157
|
+
|
|
2158
|
+
glob(result) {
|
|
2159
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2160
|
+
const { pattern, matches, truncated } = result;
|
|
2161
|
+
const header = pattern ? `[glob: "${pattern}"]` : '';
|
|
2162
|
+
if (!Array.isArray(matches) || matches.length === 0) return `${header}\nNo files found.`;
|
|
2163
|
+
if (matches.length <= 50) {
|
|
2164
|
+
return `${header}\n${matches.join('\n')}`;
|
|
2165
|
+
}
|
|
2166
|
+
const shown = matches.slice(0, 50);
|
|
2167
|
+
return `${header}\n${shown.join('\n')}\n... and ${matches.length - 50} more files [total: ${matches.length}${truncated ? ', results were truncated' : ''}]`;
|
|
2168
|
+
},
|
|
2169
|
+
|
|
2170
|
+
list(result) {
|
|
2171
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2172
|
+
if (!Array.isArray(result.items)) return JSON.stringify(result);
|
|
2173
|
+
const header = result.path ? `[${result.path}]` : '';
|
|
2174
|
+
const dirs = result.items.filter((i) => i.type === 'dir').map((i) => `${i.name}/`);
|
|
2175
|
+
const files = result.items.filter((i) => i.type === 'file').map((i) => i.name);
|
|
2176
|
+
return `${header}\n${dirs.join('\n')}${dirs.length && files.length ? '\n' : ''}${files.join('\n')}`;
|
|
2177
|
+
},
|
|
2178
|
+
|
|
2179
|
+
edit(result) {
|
|
2180
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2181
|
+
const p = result.path || '';
|
|
2182
|
+
const action = result.action || '';
|
|
2183
|
+
const line = result.changed_line || 0;
|
|
2184
|
+
const summary = `${action} ${p}${line > 0 ? ` @L${line}` : ''}`;
|
|
2185
|
+
const diffPreview = result.diff_preview || '';
|
|
2186
|
+
if (diffPreview) {
|
|
2187
|
+
const trimmed = diffPreview.length > 600 ? `${diffPreview.slice(0, 597)}...` : diffPreview;
|
|
2188
|
+
return `${summary}\n${trimmed}`;
|
|
2189
|
+
}
|
|
2190
|
+
return summary + (result.ok !== false ? '' : ` [FAILED: ${result.error || 'unknown'}]`);
|
|
2191
|
+
},
|
|
2192
|
+
|
|
2193
|
+
write(result) {
|
|
2194
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2195
|
+
const p = result.path || '';
|
|
2196
|
+
const action = result.action || 'write';
|
|
2197
|
+
const line = result.changed_line || 0;
|
|
2198
|
+
const summary = `${action} ${p}${line > 0 ? ` @L${line}` : ''}`;
|
|
2199
|
+
const diffPreview = result.diff_preview || '';
|
|
2200
|
+
if (diffPreview) {
|
|
2201
|
+
const trimmed = diffPreview.length > 600 ? `${diffPreview.slice(0, 597)}...` : diffPreview;
|
|
2202
|
+
return `${summary}\n${trimmed}`;
|
|
2203
|
+
}
|
|
2204
|
+
return summary;
|
|
2205
|
+
},
|
|
2206
|
+
|
|
2207
|
+
run(result) {
|
|
2208
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2209
|
+
const command = String(result.command || '').slice(0, 200);
|
|
2210
|
+
const stdout = String(result.stdout || '').slice(0, 500);
|
|
2211
|
+
const stderr = String(result.stderr || '').slice(0, 500);
|
|
2212
|
+
const code = result.code ?? 0;
|
|
2213
|
+
const parts = [`[exit: ${code}]`];
|
|
2214
|
+
if (command) parts.push(`command: ${command}`);
|
|
2215
|
+
if (stdout) parts.push(`stdout:\n${stdout}`);
|
|
2216
|
+
if (stderr) parts.push(`stderr:\n${stderr}`);
|
|
2217
|
+
return parts.join('\n');
|
|
2218
|
+
},
|
|
2219
|
+
|
|
2220
|
+
generate_diff(result) {
|
|
2221
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2222
|
+
const p = result.path || '';
|
|
2223
|
+
const diff = result.diff || '';
|
|
2224
|
+
if (diff.length <= 2000) return `${p ? `[${p}]\n` : ''}${diff}`;
|
|
2225
|
+
return `${p ? `[${p}]\n` : ''}${diff.slice(0, 1997)}...\n[diff truncated: ${diff.length} chars total]`;
|
|
2226
|
+
},
|
|
2227
|
+
|
|
2228
|
+
patch(result) {
|
|
2229
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2230
|
+
if (Array.isArray(result.files)) {
|
|
2231
|
+
const names = result.files.slice(0, 10).map((f) => typeof f === 'string' ? f : f.path || '?');
|
|
2232
|
+
return `patched ${result.files.length} file(s): ${names.join(', ')}${result.files.length > 10 ? ` ... +${result.files.length - 10} more` : ''}`;
|
|
2233
|
+
}
|
|
2234
|
+
const p = result.path || '';
|
|
2235
|
+
const line = result.changed_line || 0;
|
|
2236
|
+
return `patched ${p}${line > 0 ? ` @L${line}` : ''}${result.ok === false ? ` [FAILED: ${result.error || ''}]` : ''}`;
|
|
2237
|
+
},
|
|
2238
|
+
|
|
2239
|
+
ast_query(result) {
|
|
2240
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2241
|
+
if (!Array.isArray(result.matches)) return JSON.stringify(result);
|
|
2242
|
+
const header = `[ast_query: ${result.matches.length} match(es)]`;
|
|
2243
|
+
const lines = result.matches.slice(0, 20).map((m) => {
|
|
2244
|
+
const name = m.name || m.ast_target?.name || '?';
|
|
2245
|
+
const kind = m.kind || m.ast_target?.kind || '?';
|
|
2246
|
+
return ` ${kind} ${name}`;
|
|
2247
|
+
});
|
|
2248
|
+
return `${header}\n${lines.join('\n')}${result.matches.length > 20 ? `\n... +${result.matches.length - 20} more` : ''}`;
|
|
2249
|
+
},
|
|
2250
|
+
|
|
2251
|
+
read_ast_node(result) {
|
|
2252
|
+
if (typeof result === 'string') return result;
|
|
2253
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2254
|
+
const name = result.name || '';
|
|
2255
|
+
const kind = result.kind || '';
|
|
2256
|
+
const content = result.content || result.source || '';
|
|
2257
|
+
const header = `${kind} ${name}`;
|
|
2258
|
+
if (typeof content !== 'string' || content.length <= 2000) return `${header}\n${content}`;
|
|
2259
|
+
return `${header}\n${content.slice(0, 1200)}\n... [omitted ${content.length - 1600} chars] ...\n${content.slice(-400)}`;
|
|
2260
|
+
},
|
|
2261
|
+
|
|
2262
|
+
start_service(result) {
|
|
2263
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2264
|
+
const tid = result.task_id || '';
|
|
2265
|
+
const status = result.status || 'unknown';
|
|
2266
|
+
const confirmed = result.startup_confirmed ? 'ready' : 'starting';
|
|
2267
|
+
const url = result.url || '';
|
|
2268
|
+
return `${tid} ${status} (${confirmed})${url ? ` -> ${url}` : ''}`;
|
|
2269
|
+
},
|
|
2270
|
+
|
|
2271
|
+
list_services(result) {
|
|
2272
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2273
|
+
if (!Array.isArray(result.services)) return JSON.stringify(result);
|
|
2274
|
+
if (result.services.length === 0) return 'No services running.';
|
|
2275
|
+
return result.services.map((s) => `${s.task_id || '?'} ${s.status || 'unknown'}${s.command ? ` (${s.command.slice(0, 60)})` : ''}`).join('\n');
|
|
2276
|
+
},
|
|
2277
|
+
|
|
2278
|
+
get_service_status(result) {
|
|
2279
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2280
|
+
const tid = result.task_id || '';
|
|
2281
|
+
const status = result.status || 'unknown';
|
|
2282
|
+
const url = result.url || '';
|
|
2283
|
+
const logs = Array.isArray(result.recent_logs) ? result.recent_logs.slice(-3).join('\n') : '';
|
|
2284
|
+
return `${tid} ${status}${url ? ` -> ${url}` : ''}${logs ? `\n${logs}` : ''}`;
|
|
2285
|
+
},
|
|
2286
|
+
|
|
2287
|
+
get_service_logs(result) {
|
|
2288
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2289
|
+
const logs = Array.isArray(result.recent_logs) ? result.recent_logs.join('\n') : '';
|
|
2290
|
+
return logs || 'No recent logs.';
|
|
2291
|
+
},
|
|
2292
|
+
|
|
2293
|
+
stop_service(result) {
|
|
2294
|
+
if (!result || typeof result !== 'object') return String(result);
|
|
2295
|
+
return `${result.task_id || '?'} stopped${result.exit_code != null ? ` (exit ${result.exit_code})` : ''}`;
|
|
2296
|
+
}
|
|
2056
2297
|
};
|
|
2057
2298
|
|
|
2058
|
-
return { definitions, handlers };
|
|
2299
|
+
return { definitions, handlers, formatters, deferredDefinitions };
|
|
2059
2300
|
}
|