codemini-cli 0.2.0 → 0.2.1
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/README.md +44 -20
- package/package.json +4 -2
- package/src/cli.js +1 -1
- package/src/commands/chat.js +1 -0
- package/src/core/ast.js +310 -0
- package/src/core/chat-runtime.js +37 -15
- package/src/core/checkpoint-store.js +2 -1
- package/src/core/command-loader.js +3 -4
- package/src/core/config-store.js +6 -3
- package/src/core/default-system-prompt.js +1 -1
- package/src/core/paths.js +52 -58
- package/src/core/project-index.js +510 -0
- package/src/core/shell-profile.js +1 -1
- package/src/core/task-store.js +3 -2
- package/src/core/tools.js +173 -6
- package/src/tui/chat-app.js +111 -13
package/src/core/tools.js
CHANGED
|
@@ -13,8 +13,10 @@ import {
|
|
|
13
13
|
terminateChild
|
|
14
14
|
} from './shell.js';
|
|
15
15
|
import { evaluateCommandPolicy } from './command-policy.js';
|
|
16
|
+
import { queryAst, readAstNode, resolveAstTarget } from './ast.js';
|
|
17
|
+
import { initializeProjectIndex, refreshIndexedFile } from './project-index.js';
|
|
16
18
|
|
|
17
|
-
const SKIP_DIRS = new Set(['.git', 'node_modules', '.
|
|
19
|
+
const SKIP_DIRS = new Set(['.git', 'node_modules', '.codemini', '.codemini-global', 'dist', 'coverage']);
|
|
18
20
|
const TEXT_EXTENSIONS = new Set([
|
|
19
21
|
'.js',
|
|
20
22
|
'.jsx',
|
|
@@ -1525,11 +1527,13 @@ function normalizeEditTargetArgs(args = {}) {
|
|
|
1525
1527
|
}
|
|
1526
1528
|
return {
|
|
1527
1529
|
file,
|
|
1530
|
+
ast_target: normalizedEdit.ast_target ?? args?.ast_target,
|
|
1528
1531
|
edit: normalizedEdit
|
|
1529
1532
|
};
|
|
1530
1533
|
}
|
|
1531
1534
|
return {
|
|
1532
1535
|
file,
|
|
1536
|
+
ast_target: args?.ast_target,
|
|
1533
1537
|
edit: {
|
|
1534
1538
|
kind: args?.kind,
|
|
1535
1539
|
target: args?.target,
|
|
@@ -1545,6 +1549,7 @@ function normalizeEditTargetArgs(args = {}) {
|
|
|
1545
1549
|
async function editTarget(root, args) {
|
|
1546
1550
|
const normalized = normalizeEditTargetArgs(args);
|
|
1547
1551
|
const file = normalized.file;
|
|
1552
|
+
const astTarget = normalized.ast_target;
|
|
1548
1553
|
const edit = normalized.edit || {};
|
|
1549
1554
|
let kind = String(edit.kind || '').trim();
|
|
1550
1555
|
const hasContent = edit.new_content != null || edit.content != null;
|
|
@@ -1561,6 +1566,19 @@ async function editTarget(root, args) {
|
|
|
1561
1566
|
}
|
|
1562
1567
|
}
|
|
1563
1568
|
if (!file || !kind) throw new Error('edit requires file and edit.kind');
|
|
1569
|
+
if (astTarget) {
|
|
1570
|
+
if (kind !== 'replace_block') {
|
|
1571
|
+
throw new Error('AST-scoped edit only supports replace_block');
|
|
1572
|
+
}
|
|
1573
|
+
const resolved = await resolveAstTarget(root, file, astTarget);
|
|
1574
|
+
const beforeContent = resolved.content;
|
|
1575
|
+
const node = resolved.node;
|
|
1576
|
+
const afterContent = `${beforeContent.slice(0, node.startIndex)}${edit.new_content || ''}${beforeContent.slice(node.endIndex)}`;
|
|
1577
|
+
await fs.writeFile(resolved.absolutePath, afterContent, 'utf8');
|
|
1578
|
+
resolved.tree.delete();
|
|
1579
|
+
resolved.parser.delete();
|
|
1580
|
+
return editResult(file, 'replace_block', beforeContent, afterContent, node.startPosition.row + 1);
|
|
1581
|
+
}
|
|
1564
1582
|
if (kind === 'replace_block') {
|
|
1565
1583
|
const resolvedTarget =
|
|
1566
1584
|
edit.target ||
|
|
@@ -1614,7 +1632,85 @@ async function editTarget(root, args) {
|
|
|
1614
1632
|
throw new Error(`edit does not support kind: ${kind}`);
|
|
1615
1633
|
}
|
|
1616
1634
|
|
|
1617
|
-
export function getBuiltinTools({ workspaceRoot = process.cwd(), config }) {
|
|
1635
|
+
export function getBuiltinTools({ workspaceRoot = process.cwd(), config, onSystemEvent }) {
|
|
1636
|
+
const emitSystemTool = (event) => {
|
|
1637
|
+
if (typeof onSystemEvent === 'function' && event) onSystemEvent(event);
|
|
1638
|
+
};
|
|
1639
|
+
const astSelectionCache = new Map();
|
|
1640
|
+
let lastAstTarget = null;
|
|
1641
|
+
const rememberAstSelection = (filePath, astTarget) => {
|
|
1642
|
+
const key = String(filePath || '').trim();
|
|
1643
|
+
if (!key || !astTarget) return;
|
|
1644
|
+
lastAstTarget = astTarget;
|
|
1645
|
+
astSelectionCache.set(key, astTarget);
|
|
1646
|
+
};
|
|
1647
|
+
const hasExplicitBlockHints = (args = {}) =>
|
|
1648
|
+
Boolean(
|
|
1649
|
+
args?.ast_target ||
|
|
1650
|
+
args?.symbol ||
|
|
1651
|
+
args?.line ||
|
|
1652
|
+
args?.target ||
|
|
1653
|
+
args?.edit?.ast_target ||
|
|
1654
|
+
args?.edit?.symbol ||
|
|
1655
|
+
args?.edit?.line ||
|
|
1656
|
+
args?.edit?.target
|
|
1657
|
+
);
|
|
1658
|
+
const resolveCachedAstTarget = (args = {}, { requireAstScope = false } = {}) => {
|
|
1659
|
+
const file = String(args?.path || args?.file || args?.ast_target?.path || '').trim();
|
|
1660
|
+
if (args?.ast_target) return args.ast_target;
|
|
1661
|
+
if (file) {
|
|
1662
|
+
if (requireAstScope && hasExplicitBlockHints(args)) return null;
|
|
1663
|
+
return astSelectionCache.get(file) || lastAstTarget || null;
|
|
1664
|
+
}
|
|
1665
|
+
return lastAstTarget || null;
|
|
1666
|
+
};
|
|
1667
|
+
const ensureProjectIndex = async () => {
|
|
1668
|
+
const eventId = `project-index:${Date.now()}`;
|
|
1669
|
+
const name = 'project_index(.codemini-project/project-map.json,.codemini-project/file-index.json)';
|
|
1670
|
+
try {
|
|
1671
|
+
const result = await initializeProjectIndex(workspaceRoot);
|
|
1672
|
+
if (result?.skipped || !result?.summary) {
|
|
1673
|
+
return result;
|
|
1674
|
+
}
|
|
1675
|
+
emitSystemTool({ type: 'system_tool:end', id: eventId, name, summary: result?.summary });
|
|
1676
|
+
return result;
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
emitSystemTool({
|
|
1679
|
+
type: 'system_tool:error',
|
|
1680
|
+
id: eventId,
|
|
1681
|
+
name,
|
|
1682
|
+
summary: error instanceof Error ? error.message : String(error)
|
|
1683
|
+
});
|
|
1684
|
+
return null;
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
const refreshProjectFile = async (filePath) => {
|
|
1688
|
+
const relativePath = String(filePath || '').trim();
|
|
1689
|
+
if (!relativePath) return null;
|
|
1690
|
+
const eventId = `file-index:${relativePath}:${Date.now()}`;
|
|
1691
|
+
const name = `file_index(${relativePath})`;
|
|
1692
|
+
try {
|
|
1693
|
+
const result = await refreshIndexedFile(workspaceRoot, relativePath);
|
|
1694
|
+
if (!result?.summary) {
|
|
1695
|
+
return result;
|
|
1696
|
+
}
|
|
1697
|
+
emitSystemTool({
|
|
1698
|
+
type: 'system_tool:end',
|
|
1699
|
+
id: eventId,
|
|
1700
|
+
name,
|
|
1701
|
+
summary: result?.summary || `updated .codemini-project for ${relativePath}`
|
|
1702
|
+
});
|
|
1703
|
+
return result;
|
|
1704
|
+
} catch (error) {
|
|
1705
|
+
emitSystemTool({
|
|
1706
|
+
type: 'system_tool:error',
|
|
1707
|
+
id: eventId,
|
|
1708
|
+
name,
|
|
1709
|
+
summary: error instanceof Error ? error.message : String(error)
|
|
1710
|
+
});
|
|
1711
|
+
return null;
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1618
1714
|
const definitions = [
|
|
1619
1715
|
{
|
|
1620
1716
|
type: 'function',
|
|
@@ -1693,7 +1789,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config }) {
|
|
|
1693
1789
|
function: {
|
|
1694
1790
|
name: 'edit',
|
|
1695
1791
|
description:
|
|
1696
|
-
'Preferred edit tool for existing files. Accepts natural forms such as file + new_content for whole-file rewrites, file + symbol/line + new_content for block edits, file + old_text + new_text for exact replacements, and file + anchor_text + content for anchored inserts.
|
|
1792
|
+
'Preferred edit tool for existing files. Accepts natural forms such as file + new_content for whole-file rewrites, file + symbol/line + new_content for block edits, file + old_text + new_text for exact replacements, and file + anchor_text + content for anchored inserts. When ast_target is provided, only replace_block is allowed and the write is constrained to that exact syntax node. If a file has just been selected via ast_query, the cached ast_target may be reused when omitted.',
|
|
1697
1793
|
parameters: {
|
|
1698
1794
|
type: 'object',
|
|
1699
1795
|
properties: {
|
|
@@ -1707,6 +1803,7 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config }) {
|
|
|
1707
1803
|
position: { type: 'string' },
|
|
1708
1804
|
kind: { type: 'string' },
|
|
1709
1805
|
target: { type: 'object' },
|
|
1806
|
+
ast_target: { type: 'object' },
|
|
1710
1807
|
symbol: { type: 'string' },
|
|
1711
1808
|
line: { type: 'number' },
|
|
1712
1809
|
edit: { type: 'object' },
|
|
@@ -1715,6 +1812,42 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config }) {
|
|
|
1715
1812
|
}
|
|
1716
1813
|
}
|
|
1717
1814
|
},
|
|
1815
|
+
{
|
|
1816
|
+
type: 'function',
|
|
1817
|
+
function: {
|
|
1818
|
+
name: 'ast_query',
|
|
1819
|
+
description:
|
|
1820
|
+
'Run a Tree-sitter query against a code file and return explicit ast_target objects that can be passed into read_ast_node or edit for node-scoped changes. Prefer the returned ast_target verbatim in the next read_ast_node or edit call.',
|
|
1821
|
+
parameters: {
|
|
1822
|
+
type: 'object',
|
|
1823
|
+
properties: {
|
|
1824
|
+
path: { type: 'string' },
|
|
1825
|
+
language: { type: 'string' },
|
|
1826
|
+
query: { type: 'string' },
|
|
1827
|
+
capture_name: { type: 'string' },
|
|
1828
|
+
max_results: { type: 'number' }
|
|
1829
|
+
},
|
|
1830
|
+
required: ['path', 'query']
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
type: 'function',
|
|
1836
|
+
function: {
|
|
1837
|
+
name: 'read_ast_node',
|
|
1838
|
+
description:
|
|
1839
|
+
'Read the current source and compact structural context for a previously selected AST node using ast_target. If omitted, the most recent ast_query selection for the same file may be reused.',
|
|
1840
|
+
parameters: {
|
|
1841
|
+
type: 'object',
|
|
1842
|
+
properties: {
|
|
1843
|
+
path: { type: 'string' },
|
|
1844
|
+
language: { type: 'string' },
|
|
1845
|
+
ast_target: { type: 'object' }
|
|
1846
|
+
},
|
|
1847
|
+
required: ['path', 'ast_target']
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
},
|
|
1718
1851
|
{
|
|
1719
1852
|
type: 'function',
|
|
1720
1853
|
function: {
|
|
@@ -1876,10 +2009,44 @@ export function getBuiltinTools({ workspaceRoot = process.cwd(), config }) {
|
|
|
1876
2009
|
grep: (args) => grep(workspaceRoot, args),
|
|
1877
2010
|
glob: (args) => glob(workspaceRoot, args),
|
|
1878
2011
|
list: (args) => list(workspaceRoot, args),
|
|
1879
|
-
|
|
2012
|
+
ast_query: async (args) => {
|
|
2013
|
+
const result = await queryAst(workspaceRoot, args);
|
|
2014
|
+
const firstTarget = result?.matches?.[0]?.ast_target;
|
|
2015
|
+
if (firstTarget?.path) rememberAstSelection(firstTarget.path, firstTarget);
|
|
2016
|
+
return result;
|
|
2017
|
+
},
|
|
2018
|
+
read_ast_node: (args) => {
|
|
2019
|
+
const astTarget = resolveCachedAstTarget(args);
|
|
2020
|
+
if (!astTarget) throw new Error('read_ast_node requires ast_target or a prior ast_query on the same file');
|
|
2021
|
+
if (astTarget.path) rememberAstSelection(astTarget.path, astTarget);
|
|
2022
|
+
return readAstNode(workspaceRoot, { ...args, ast_target: astTarget });
|
|
2023
|
+
},
|
|
2024
|
+
edit: async (args) => {
|
|
2025
|
+
await ensureProjectIndex();
|
|
2026
|
+
const normalizedKind = String(args?.edit?.kind || args?.kind || '').trim();
|
|
2027
|
+
const astTarget = resolveCachedAstTarget(args, { requireAstScope: normalizedKind === 'replace_block' });
|
|
2028
|
+
const result = await editTarget(workspaceRoot, astTarget ? { ...args, ast_target: astTarget } : args);
|
|
2029
|
+
if (result?.path) await refreshProjectFile(result.path);
|
|
2030
|
+
return result;
|
|
2031
|
+
},
|
|
1880
2032
|
generate_diff: (args) => generateDiff(workspaceRoot, args),
|
|
1881
|
-
patch: (args) =>
|
|
1882
|
-
|
|
2033
|
+
patch: async (args) => {
|
|
2034
|
+
await ensureProjectIndex();
|
|
2035
|
+
const result = await applyPatch(workspaceRoot, args);
|
|
2036
|
+
if (result?.path) await refreshProjectFile(result.path);
|
|
2037
|
+
if (Array.isArray(result?.files)) {
|
|
2038
|
+
for (const item of result.files) {
|
|
2039
|
+
if (item?.path) await refreshProjectFile(item.path);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return result;
|
|
2043
|
+
},
|
|
2044
|
+
write: async (args) => {
|
|
2045
|
+
await ensureProjectIndex();
|
|
2046
|
+
const result = await writeFile(workspaceRoot, args);
|
|
2047
|
+
if (result?.path) await refreshProjectFile(result.path);
|
|
2048
|
+
return result;
|
|
2049
|
+
},
|
|
1883
2050
|
run: (args) => runCommand(workspaceRoot, config, args),
|
|
1884
2051
|
start_service: (args) => startService(workspaceRoot, config, args),
|
|
1885
2052
|
list_services: () => listServices(workspaceRoot),
|
package/src/tui/chat-app.js
CHANGED
|
@@ -140,6 +140,10 @@ const TUI_COPY = {
|
|
|
140
140
|
doingCodeGeneration: '正在生成代码',
|
|
141
141
|
doneSkill: '已完成技能',
|
|
142
142
|
doingSkill: '正在执行技能',
|
|
143
|
+
doneProjectIndex: '已初始化项目索引',
|
|
144
|
+
doingProjectIndex: '正在初始化项目索引',
|
|
145
|
+
doneFileIndex: '已刷新文件索引',
|
|
146
|
+
doingFileIndex: '正在刷新文件索引',
|
|
143
147
|
toolFailed: (name) => `工具执行失败: ${name}`,
|
|
144
148
|
waitingModelContinue: (detail) => `${detail},等待模型继续`,
|
|
145
149
|
waitingModelAdjust: (detail) => `${detail},等待模型调整`
|
|
@@ -266,6 +270,10 @@ const TUI_COPY = {
|
|
|
266
270
|
doingCodeGeneration: 'Generating code',
|
|
267
271
|
doneSkill: 'Completed skill',
|
|
268
272
|
doingSkill: 'Running skill',
|
|
273
|
+
doneProjectIndex: 'Project index initialized',
|
|
274
|
+
doingProjectIndex: 'Initializing project index',
|
|
275
|
+
doneFileIndex: 'File index refreshed',
|
|
276
|
+
doingFileIndex: 'Refreshing file index',
|
|
269
277
|
toolFailed: (name) => `Tool failed: ${name}`,
|
|
270
278
|
waitingModelContinue: (detail) => `${detail}, waiting for model to continue`,
|
|
271
279
|
waitingModelAdjust: (detail) => `${detail}, waiting for model to adjust`
|
|
@@ -431,6 +439,12 @@ function getActivityDisplayParts(activity) {
|
|
|
431
439
|
secondary: `(${activity?.name || 'unknown'})`
|
|
432
440
|
};
|
|
433
441
|
}
|
|
442
|
+
if ((activity?.type || 'tool') === 'system_tool') {
|
|
443
|
+
return {
|
|
444
|
+
primary: 'Index',
|
|
445
|
+
secondary: parsed.target ? `(${parsed.target})` : parsed.base ? `(${parsed.base})` : ''
|
|
446
|
+
};
|
|
447
|
+
}
|
|
434
448
|
const labels = {
|
|
435
449
|
read: 'Read',
|
|
436
450
|
edit: 'Edit',
|
|
@@ -457,6 +471,21 @@ function getActivityDisplayParts(activity) {
|
|
|
457
471
|
|
|
458
472
|
function describeToolActivity(name, copy, { done = false, blocked = false } = {}) {
|
|
459
473
|
const parsed = parseToolDisplayName(name);
|
|
474
|
+
if (parsed.base === 'project_index') {
|
|
475
|
+
return blocked
|
|
476
|
+
? `${copy.toolActivity.blocked}: project index`
|
|
477
|
+
: done
|
|
478
|
+
? copy.toolActivity.doneProjectIndex
|
|
479
|
+
: copy.toolActivity.doingProjectIndex;
|
|
480
|
+
}
|
|
481
|
+
if (parsed.base === 'file_index') {
|
|
482
|
+
const safeTarget = trimText(parsed.target || '.codemini-project/file-index.json', 72);
|
|
483
|
+
return blocked
|
|
484
|
+
? `${copy.toolActivity.blocked}: ${safeTarget}`
|
|
485
|
+
: done
|
|
486
|
+
? `${copy.toolActivity.doneFileIndex}: ${safeTarget}`
|
|
487
|
+
: `${copy.toolActivity.doingFileIndex}: ${safeTarget}`;
|
|
488
|
+
}
|
|
460
489
|
if (parsed.base === 'run' || parsed.base === 'start_service') {
|
|
461
490
|
const intent = classifyCommandIntent(parsed.target);
|
|
462
491
|
const target = parsed.target || intent.kind || 'command';
|
|
@@ -765,8 +794,11 @@ function PlanStrip({ planState, copy }) {
|
|
|
765
794
|
);
|
|
766
795
|
}
|
|
767
796
|
|
|
768
|
-
function Header({ sessionId, model, shellName }) {
|
|
797
|
+
function Header({ sessionId, model, shellName, safeMode = true }) {
|
|
769
798
|
const shortSession = String(sessionId || '').slice(-12) || '-';
|
|
799
|
+
const modeValue = safeMode ? 'SAFE' : 'OPEN';
|
|
800
|
+
const modeColor = safeMode ? 'greenBright' : 'redBright';
|
|
801
|
+
const modeTextColor = safeMode ? 'black' : 'white';
|
|
770
802
|
return h(
|
|
771
803
|
Box,
|
|
772
804
|
{ width: '100%', justifyContent: 'center', marginTop: 1, marginBottom: 2 },
|
|
@@ -781,12 +813,6 @@ function Header({ sessionId, model, shellName }) {
|
|
|
781
813
|
alignItems: 'center',
|
|
782
814
|
minWidth: 88
|
|
783
815
|
},
|
|
784
|
-
h(
|
|
785
|
-
Box,
|
|
786
|
-
{ width: '100%', justifyContent: 'space-between', marginBottom: 1 },
|
|
787
|
-
h(Text, { color: 'cyan' }, 'CLI'),
|
|
788
|
-
h(Text, { color: 'greenBright' }, 'SAFE')
|
|
789
|
-
),
|
|
790
816
|
...BANNER.map((line, idx) =>
|
|
791
817
|
h(
|
|
792
818
|
Box,
|
|
@@ -801,8 +827,9 @@ function Header({ sessionId, model, shellName }) {
|
|
|
801
827
|
Box,
|
|
802
828
|
{ flexDirection: 'row', justifyContent: 'center' },
|
|
803
829
|
h(StatusPill, { label: 'MODEL', value: model, color: 'cyanBright', textColor: 'black' }),
|
|
804
|
-
h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: '
|
|
805
|
-
h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' })
|
|
830
|
+
h(StatusPill, { label: 'SHELL', value: shellName || 'powershell', color: 'yellowBright', textColor: 'black' }),
|
|
831
|
+
h(StatusPill, { label: 'SESSION', value: shortSession, color: 'magentaBright', textColor: 'black' }),
|
|
832
|
+
h(StatusPill, { label: 'MODE', value: modeValue, color: modeColor, textColor: modeTextColor })
|
|
806
833
|
)
|
|
807
834
|
)
|
|
808
835
|
);
|
|
@@ -1498,10 +1525,12 @@ function buildMessageRows(msg, showToolDetails, contentWidth = 72, copy) {
|
|
|
1498
1525
|
};
|
|
1499
1526
|
|
|
1500
1527
|
if (Array.isArray(msg?.segments) && msg.segments.length > 0) {
|
|
1501
|
-
const totalTools = msg.segments.filter(
|
|
1528
|
+
const totalTools = msg.segments.filter(
|
|
1529
|
+
(segment) => segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool'
|
|
1530
|
+
).length;
|
|
1502
1531
|
let toolIndex = 0;
|
|
1503
1532
|
for (const segment of msg.segments) {
|
|
1504
|
-
if (segment.type === 'tool' || segment.type === 'skill') {
|
|
1533
|
+
if (segment.type === 'tool' || segment.type === 'skill' || segment.type === 'system_tool') {
|
|
1505
1534
|
pushActivityRows(segment, toolIndex, totalTools);
|
|
1506
1535
|
toolIndex += 1;
|
|
1507
1536
|
} else {
|
|
@@ -1548,6 +1577,10 @@ function renderMessageRow(msg, row, idx, loaderTick) {
|
|
|
1548
1577
|
? row.status === 'error'
|
|
1549
1578
|
? 'redBright'
|
|
1550
1579
|
: 'cyanBright'
|
|
1580
|
+
: activity.type === 'system_tool'
|
|
1581
|
+
? row.status === 'error' || row.status === 'blocked'
|
|
1582
|
+
? 'redBright'
|
|
1583
|
+
: 'blueBright'
|
|
1551
1584
|
: row.status === 'error' || row.status === 'blocked'
|
|
1552
1585
|
? 'redBright'
|
|
1553
1586
|
: 'cyanBright';
|
|
@@ -2017,7 +2050,7 @@ function makeIdleStatus(copy, snapshot, variant = 'ready') {
|
|
|
2017
2050
|
);
|
|
2018
2051
|
}
|
|
2019
2052
|
|
|
2020
|
-
export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '' }) {
|
|
2053
|
+
export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName = 'powershell', version = '', safeMode = true }) {
|
|
2021
2054
|
const copy = getCopy(language);
|
|
2022
2055
|
const stdoutCols = Number(process.stdout?.columns || 120);
|
|
2023
2056
|
const [inputValue, setInputValue] = useState('');
|
|
@@ -2060,6 +2093,23 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2060
2093
|
const messagesRef = useRef([]);
|
|
2061
2094
|
const pendingQueueRef = useRef([]);
|
|
2062
2095
|
const deltaBufferRef = useRef('');
|
|
2096
|
+
|
|
2097
|
+
useEffect(() => {
|
|
2098
|
+
const rawStartupActivities = runtime.consumeStartupEvents?.();
|
|
2099
|
+
const startupActivities = Array.isArray(rawStartupActivities) ? rawStartupActivities : [];
|
|
2100
|
+
if (startupActivities.length === 0) return;
|
|
2101
|
+
setMessages((prev) => [
|
|
2102
|
+
...prev,
|
|
2103
|
+
{
|
|
2104
|
+
id: nextId(),
|
|
2105
|
+
label: 'system',
|
|
2106
|
+
text: '',
|
|
2107
|
+
color: 'yellowBright',
|
|
2108
|
+
toolCalls: startupActivities,
|
|
2109
|
+
segments: startupActivities
|
|
2110
|
+
}
|
|
2111
|
+
]);
|
|
2112
|
+
}, [runtime]);
|
|
2063
2113
|
const deltaFlushTimerRef = useRef(null);
|
|
2064
2114
|
const escSeqRef = useRef('');
|
|
2065
2115
|
const planTextBufferRef = useRef('');
|
|
@@ -2582,6 +2632,54 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
2582
2632
|
arguments: event.arguments
|
|
2583
2633
|
});
|
|
2584
2634
|
}
|
|
2635
|
+
if (event?.type === 'system_tool:start') {
|
|
2636
|
+
ensureActiveAssistant();
|
|
2637
|
+
const detail = describeToolActivity(event.name, copy);
|
|
2638
|
+
setRuntimeStatus(makeStatus(copy.runtime.toolRunning, detail, 'blueBright'));
|
|
2639
|
+
setInputStage('tooling');
|
|
2640
|
+
updateActivityStatusOnActiveAssistant({
|
|
2641
|
+
type: 'system_tool',
|
|
2642
|
+
id: event.id,
|
|
2643
|
+
name: event.name,
|
|
2644
|
+
status: 'running',
|
|
2645
|
+
summary: event.summary
|
|
2646
|
+
});
|
|
2647
|
+
setActiveAssistantMeta({ loading: true, phase: 'tooling', liveStatus: detail });
|
|
2648
|
+
}
|
|
2649
|
+
if (event?.type === 'system_tool:end') {
|
|
2650
|
+
const detail = describeToolActivity(event.name, copy, { done: true });
|
|
2651
|
+
setRuntimeStatus(makeStatus(copy.runtime.toolCompleted, copy.toolActivity.waitingModelContinue(detail), 'blueBright'));
|
|
2652
|
+
setInputStage('thinking');
|
|
2653
|
+
updateActivityStatusOnActiveAssistant({
|
|
2654
|
+
type: 'system_tool',
|
|
2655
|
+
id: event.id,
|
|
2656
|
+
name: event.name,
|
|
2657
|
+
status: 'done',
|
|
2658
|
+
summary: event.summary
|
|
2659
|
+
});
|
|
2660
|
+
setActiveAssistantMeta({
|
|
2661
|
+
loading: true,
|
|
2662
|
+
phase: 'thinking',
|
|
2663
|
+
liveStatus: copy.toolActivity.waitingModelContinue(detail)
|
|
2664
|
+
});
|
|
2665
|
+
}
|
|
2666
|
+
if (event?.type === 'system_tool:error') {
|
|
2667
|
+
const detail = copy.toolActivity.toolFailed(event.name);
|
|
2668
|
+
setRuntimeStatus(makeStatus(copy.runtime.toolFailed, event.summary || detail, 'redBright'));
|
|
2669
|
+
setInputStage('thinking');
|
|
2670
|
+
updateActivityStatusOnActiveAssistant({
|
|
2671
|
+
type: 'system_tool',
|
|
2672
|
+
id: event.id,
|
|
2673
|
+
name: event.name,
|
|
2674
|
+
status: 'error',
|
|
2675
|
+
summary: event.summary
|
|
2676
|
+
});
|
|
2677
|
+
setActiveAssistantMeta({
|
|
2678
|
+
loading: true,
|
|
2679
|
+
phase: 'thinking',
|
|
2680
|
+
liveStatus: copy.toolActivity.waitingModelAdjust(detail)
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2585
2683
|
if (event?.type === 'skill:start') {
|
|
2586
2684
|
ensureActiveAssistant();
|
|
2587
2685
|
const detail = describeSkillActivity(event.name, copy);
|
|
@@ -3087,7 +3185,7 @@ export function ChatApp({ runtime, sessionId, model, language = 'zh', shellName
|
|
|
3087
3185
|
return h(
|
|
3088
3186
|
Box,
|
|
3089
3187
|
{ flexDirection: 'column' },
|
|
3090
|
-
h(Header, { sessionId: displaySessionId, model: displayModel, shellName }),
|
|
3188
|
+
h(Header, { sessionId: displaySessionId, model: displayModel, shellName, safeMode }),
|
|
3091
3189
|
h(MessageList, {
|
|
3092
3190
|
messages: visibleMessages,
|
|
3093
3191
|
loaderTick,
|