zelari-code 0.6.2 → 0.7.5
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/bin/zelari-code.js +1 -1
- package/dist/agents/councilApi.js +7 -7
- package/dist/agents/councilApi.js.map +1 -1
- package/dist/agents/councilDirectives.js +3 -3
- package/dist/agents/promoteMember.js +2 -2
- package/dist/agents/roles.js +50 -32
- package/dist/agents/roles.js.map +1 -1
- package/dist/agents/skills/builtin/debugging.js +4 -4
- package/dist/agents/skills/builtin/debugging.js.map +1 -1
- package/dist/agents/skills/builtin/docs.js +23 -13
- package/dist/agents/skills/builtin/docs.js.map +1 -1
- package/dist/agents/skills/builtin/git-ops.js +3 -3
- package/dist/agents/skills/builtin/git-ops.js.map +1 -1
- package/dist/agents/skills/builtin/planning.js +4 -4
- package/dist/agents/skills/builtin/planning.js.map +1 -1
- package/dist/agents/skills/builtin/refactoring.js +3 -3
- package/dist/agents/skills/builtin/refactoring.js.map +1 -1
- package/dist/agents/skills/builtin/review.js +9 -9
- package/dist/agents/skills/builtin/review.js.map +1 -1
- package/dist/agents/skills/builtin/testing.js +3 -3
- package/dist/agents/skills/builtin/testing.js.map +1 -1
- package/dist/agents/tools.js +2 -9
- package/dist/agents/tools.js.map +1 -1
- package/dist/cli/app.js +63 -29
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/components/ChatStream.js +40 -126
- package/dist/cli/components/ChatStream.js.map +1 -1
- package/dist/cli/components/CollapsibleToolOutput.js +8 -26
- package/dist/cli/components/CollapsibleToolOutput.js.map +1 -1
- package/dist/cli/components/Header.js +6 -18
- package/dist/cli/components/Header.js.map +1 -1
- package/dist/cli/components/LiveRegion.js +54 -0
- package/dist/cli/components/LiveRegion.js.map +1 -0
- package/dist/cli/components/Sidebar.js +33 -31
- package/dist/cli/components/Sidebar.js.map +1 -1
- package/dist/cli/components/StatusBar.js +42 -0
- package/dist/cli/components/StatusBar.js.map +1 -0
- package/dist/cli/components/ToolOutput.js +68 -0
- package/dist/cli/components/ToolOutput.js.map +1 -0
- package/dist/cli/components/toolFormat.js +195 -0
- package/dist/cli/components/toolFormat.js.map +1 -0
- package/dist/cli/grokOAuth.js +93 -75
- package/dist/cli/grokOAuth.js.map +1 -1
- package/dist/cli/hooks/chatState.js +139 -0
- package/dist/cli/hooks/chatState.js.map +1 -0
- package/dist/cli/hooks/eventsToMessages.js +1 -1
- package/dist/cli/hooks/eventsToMessages.js.map +1 -1
- package/dist/cli/hooks/messageHelpers.js +11 -4
- package/dist/cli/hooks/messageHelpers.js.map +1 -1
- package/dist/cli/hooks/useBatchedMessages.js +14 -10
- package/dist/cli/hooks/useBatchedMessages.js.map +1 -1
- package/dist/cli/hooks/useChatTurn.js +400 -87
- package/dist/cli/hooks/useChatTurn.js.map +1 -1
- package/dist/cli/hooks/useSession.js +23 -11
- package/dist/cli/hooks/useSession.js.map +1 -1
- package/dist/cli/hooks/useSlashDispatch.js +9 -1
- package/dist/cli/hooks/useSlashDispatch.js.map +1 -1
- package/dist/cli/main.bundled.js +16972 -15277
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +35 -1
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/mcp/mcpClient.js +152 -0
- package/dist/cli/mcp/mcpClient.js.map +1 -0
- package/dist/cli/mcp/mcpManager.js +139 -0
- package/dist/cli/mcp/mcpManager.js.map +1 -0
- package/dist/cli/refreshRegistry.js +9 -6
- package/dist/cli/refreshRegistry.js.map +1 -1
- package/dist/cli/skillsMd.js +155 -0
- package/dist/cli/skillsMd.js.map +1 -0
- package/dist/cli/slashHandlers/skills.js +16 -0
- package/dist/cli/slashHandlers/skills.js.map +1 -1
- package/dist/cli/toolRegistry.js +31 -0
- package/dist/cli/toolRegistry.js.map +1 -1
- package/dist/cli/wizard/index.js +1 -1
- package/dist/cli/workspace/paths.js +20 -13
- package/dist/cli/workspace/paths.js.map +1 -1
- package/dist/cli/workspace/storage.js +7 -3
- package/dist/cli/workspace/storage.js.map +1 -1
- package/dist/cli/workspace/stubs.js +122 -44
- package/dist/cli/workspace/stubs.js.map +1 -1
- package/dist/cli/workspace/workspaceSummary.js +255 -0
- package/dist/cli/workspace/workspaceSummary.js.map +1 -0
- package/dist/main/core/AgentHarness.js +36 -7
- package/dist/main/core/AgentHarness.js.map +1 -1
- package/dist/main/core/tools/builtin/listFiles.js +43 -5
- package/dist/main/core/tools/builtin/listFiles.js.map +1 -1
- package/dist/main/core/tools/builtin/search.js +28 -120
- package/dist/main/core/tools/builtin/search.js.map +1 -1
- package/package.json +2 -2
- package/dist/cli/oauthCallbackServer.js +0 -207
- package/dist/cli/oauthCallbackServer.js.map +0 -1
- package/dist/main/core/tools/builtin/_walk.js +0 -129
- package/dist/main/core/tools/builtin/_walk.js.map +0 -1
- package/dist/main/core/tools/builtin/diff.js +0 -477
- package/dist/main/core/tools/builtin/diff.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatStream.js","sourceRoot":"","sources":["../../../src/cli/components/ChatStream.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ChatStream.js","sourceRoot":"","sources":["../../../src/cli/components/ChatStream.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwC7C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa,CAAC,CAAc,EAAE,IAAI,GAAG,KAAK;IACxD,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,CACL,oBAAC,UAAU,IACT,GAAG,EAAE,CAAC,CAAC,EAAE,EACT,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,MAAM,EAC9B,OAAO,EAAE,CAAC,CAAC,OAAO,EAClB,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO,EAC/B,EAAE,EAAE,CAAC,CAAC,MAAM,EACZ,UAAU,EAAE,CAAC,CAAC,cAAc,EAC5B,IAAI,EAAE,IAAI,GACV,CACH,CAAC;IACJ,CAAC;IACD,OAAO,CACL,oBAAC,GAAG,IAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,aAAa,EAAC,QAAQ,EAAC,YAAY,EAAE,CAAC;QACpD,oBAAC,IAAI,IAAC,KAAK,EAAE,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI;YACxF,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;;YAAG,CAAC,CAAC,IAAI;YACrE,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CACxC,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS;;gBAAK,CAAC,CAAC,UAAU,CAAQ,CAC/C,CAAC,CAAC,CAAC,IAAI,CACH;QACP,oBAAC,GAAG,IAAC,UAAU,EAAE,CAAC;YAChB,oBAAC,IAAI,QAAE,CAAC,CAAC,OAAO,CAAQ,CACpB,CACF,CACP,CAAC;AACJ,CAAC;AAiBD,SAAS,cAAc,CAAC,EAAE,QAAQ,EAAmB;IACnD,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,IACjD,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CACvB,oBAAC,IAAI,IAAC,QAAQ,qEAAsE,CACrF,CAAC,CAAC,CAAC,CACF,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CACtC,CACG,CACP,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;AAErD,8EAA8E;AAC9E,4EAA4E;AAC5E,0EAA0E;AAC1E,8EAA8E;AAC9E,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgC,EAChC,OAAe,EACf,MAAc;IAEd,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
function borderColor(toolName, ok) {
|
|
4
4
|
if (ok === false)
|
|
@@ -12,26 +12,13 @@ function borderColor(toolName, ok) {
|
|
|
12
12
|
return 'red';
|
|
13
13
|
return 'cyan';
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
export function CollapsibleToolOutput(props) {
|
|
16
16
|
const { toolName, summary, body, durationMs, ok, defaultExpanded = false } = props;
|
|
17
17
|
const isControlled = typeof props.expanded === 'boolean';
|
|
18
18
|
const [localExpanded, setLocalExpanded] = useState(defaultExpanded);
|
|
19
|
-
// Sync uncontrolled state when the controlled prop is undefined and
|
|
20
|
-
// defaultExpanded changes post-mount (rare but possible on session
|
|
21
|
-
// resume, where the restored message carries defaultExpanded=true).
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (!isControlled)
|
|
24
|
-
setLocalExpanded(defaultExpanded);
|
|
25
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
-
}, [defaultExpanded, isControlled]);
|
|
27
19
|
const expanded = isControlled ? props.expanded : localExpanded;
|
|
28
20
|
const color = borderColor(toolName, ok);
|
|
29
|
-
// Status glyph: pending (no verdict yet) / ok / error. Check `ok` first
|
|
30
|
-
// so undefined-with-defined-duration (rare but possible) shows ⋯ not ✓.
|
|
31
|
-
// v0.6.2 audit LOW-4.
|
|
32
|
-
const status = ok === true ? '✓' : ok === false ? '✗' : '⋯';
|
|
33
21
|
const summaryLine = [
|
|
34
|
-
status,
|
|
35
22
|
`[${toolName}]`,
|
|
36
23
|
summary,
|
|
37
24
|
durationMs !== undefined ? `(${durationMs}ms)` : '',
|
|
@@ -40,18 +27,13 @@ function CollapsibleToolOutputImpl(props) {
|
|
|
40
27
|
return (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
41
28
|
React.createElement(Box, null,
|
|
42
29
|
React.createElement(Text, { color: color }, summaryLine)),
|
|
43
|
-
expanded && (React.createElement(Box, { flexDirection: "column", marginLeft: 2, borderStyle: "single", borderColor: color, paddingX: 1 },
|
|
44
|
-
|
|
30
|
+
expanded && (React.createElement(Box, { flexDirection: "column", marginLeft: 2, borderStyle: "single", borderColor: color, paddingX: 1 }, body.split('\n').map((line, idx) => (
|
|
31
|
+
// B12 fix — index keys caused "Encountered two children with the
|
|
32
|
+
// same key" warnings when adjacent lines were identical (e.g.
|
|
33
|
+
// empty separators in multi-line tool output). Use line content
|
|
34
|
+
// with an idx fallback for empty lines.
|
|
35
|
+
React.createElement(Text, { key: line || `__blank-${idx}`, dimColor: ok === false }, line)))))));
|
|
45
36
|
}
|
|
46
|
-
export const CollapsibleToolOutput = React.memo(CollapsibleToolOutputImpl, (prev, next) => {
|
|
47
|
-
return (prev.toolName === next.toolName &&
|
|
48
|
-
prev.summary === next.summary &&
|
|
49
|
-
prev.body === next.body &&
|
|
50
|
-
prev.durationMs === next.durationMs &&
|
|
51
|
-
prev.ok === next.ok &&
|
|
52
|
-
prev.expanded === next.expanded &&
|
|
53
|
-
prev.defaultExpanded === next.defaultExpanded);
|
|
54
|
-
});
|
|
55
37
|
/**
|
|
56
38
|
* Pure helper: classify tool color (exported for unit tests).
|
|
57
39
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CollapsibleToolOutput.js","sourceRoot":"","sources":["../../../src/cli/components/CollapsibleToolOutput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"CollapsibleToolOutput.js","sourceRoot":"","sources":["../../../src/cli/components/CollapsibleToolOutput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAqChC,SAAS,WAAW,CAAC,QAAgB,EAAE,EAAY;IACjD,IAAI,EAAE,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,OAAO,CAAC;IACpH,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,QAAQ,CAAC;IACvE,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAC5E,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAiC;IACrE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,eAAe,GAAG,KAAK,EAAE,GAAG,KAAK,CAAC;IACnF,MAAM,YAAY,GAAG,OAAO,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC;IACzD,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAE,KAAK,CAAC,QAAoB,CAAC,CAAC,CAAC,aAAa,CAAC;IAC5E,MAAM,KAAK,GAAG,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAExC,MAAM,WAAW,GAAG;QAClB,IAAI,QAAQ,GAAG;QACf,OAAO;QACP,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,UAAU,KAAK,CAAC,CAAC,CAAC,EAAE;QACnD,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KACrB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE5B,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAE,CAAC;QACvC,oBAAC,GAAG;YACF,oBAAC,IAAI,IAAC,KAAK,EAAE,KAAK,IAAG,WAAW,CAAQ,CACpC;QACL,QAAQ,IAAI,CACX,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,UAAU,EAAE,CAAC,EAAE,WAAW,EAAC,QAAQ,EAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,IAC5F,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;QACnC,iEAAiE;QACjE,8DAA8D;QAC9D,gEAAgE;QAChE,wCAAwC;QACxC,oBAAC,IAAI,IAAC,GAAG,EAAE,IAAI,IAAI,WAAW,GAAG,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,KAAK,IAAG,IAAI,CAAQ,CAC3E,CAAC,CACE,CACP,CACG,CACP,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,EAAY;IAC9D,OAAO,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { formatCost, formatTokens } from '../modelPricing.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* re-renders only when one of its primitive props actually changes. This is
|
|
9
|
-
* critical because the parent App re-renders on EVERY streaming token delta
|
|
10
|
-
* (≈20-50/sec during LLM response); without memo the Header would redraw its
|
|
11
|
-
* border 50×/sec, causing visible flicker on the top edge of the terminal.
|
|
5
|
+
* B11 fix — wrapped in React.memo so the header doesn't re-render on every
|
|
6
|
+
* input keystroke or streaming delta. It only re-renders when its props
|
|
7
|
+
* (model, provider, cost, token count, etc.) actually change.
|
|
12
8
|
*/
|
|
13
9
|
function HeaderImpl({ model, provider, skillCount, sessionActive, sessionId, totalTokens, totalCostUsd }) {
|
|
14
10
|
const hasCost = typeof totalCostUsd === 'number' && totalCostUsd > 0;
|
|
@@ -37,14 +33,6 @@ function HeaderImpl({ model, provider, skillCount, sessionActive, sessionId, tot
|
|
|
37
33
|
tokStr,
|
|
38
34
|
" tok")));
|
|
39
35
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return (prev.model === next.model &&
|
|
43
|
-
prev.provider === next.provider &&
|
|
44
|
-
prev.skillCount === next.skillCount &&
|
|
45
|
-
prev.sessionActive === next.sessionActive &&
|
|
46
|
-
prev.sessionId === next.sessionId &&
|
|
47
|
-
prev.totalTokens === next.totalTokens &&
|
|
48
|
-
prev.totalCostUsd === next.totalCostUsd);
|
|
49
|
-
});
|
|
36
|
+
/** B11 — memoized export. Shallow equality suffices (all props are primitives). */
|
|
37
|
+
export const Header = memo(HeaderImpl);
|
|
50
38
|
//# sourceMappingURL=Header.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Header.js","sourceRoot":"","sources":["../../../src/cli/components/Header.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Header.js","sourceRoot":"","sources":["../../../src/cli/components/Header.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAc9D;;;;GAIG;AACH,SAAS,UAAU,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAe;IACnH,MAAM,OAAO,GAAG,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,GAAG,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IAC9C,OAAO,CACL,oBAAC,GAAG,IAAC,WAAW,EAAC,OAAO,EAAC,WAAW,EAAC,MAAM,EAAC,QAAQ,EAAE,CAAC,EAAE,aAAa,EAAC,KAAK;QAC1E,oBAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,MAAM,kBAAmB;QAC1C,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,QAAE,KAAK,CAAQ;QACpB,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,QAAE,QAAQ,CAAQ;QACvB,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI;YAAE,UAAU;sBAAe;QAChC,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,IAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,IAC1C,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CACjC;QACP,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,IAAC,QAAQ;;YAAU,SAAS,CAAQ;QACzC,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,IAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAG,OAAO,CAAQ;QAC1D,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,IAAC,KAAK,EAAC,MAAM;YAAE,MAAM;mBAAY,CAClC,CACP,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { renderMessage } from './ChatStream.js';
|
|
4
|
+
/**
|
|
5
|
+
* Max lines of the streaming bubble kept in the dynamic region. The full
|
|
6
|
+
* text is never lost — it lands complete in `<Static>` at finalize. This
|
|
7
|
+
* clamp only bounds the live repaint footprint so the dynamic region fits
|
|
8
|
+
* any terminal height, eliminating full-screen repaints by construction.
|
|
9
|
+
*/
|
|
10
|
+
const LIVE_STREAM_TAIL_LINES = 10;
|
|
11
|
+
/**
|
|
12
|
+
* LiveRegion — the small dynamic region Ink repaints every frame.
|
|
13
|
+
*
|
|
14
|
+
* Contains:
|
|
15
|
+
* - the currently-streaming assistant bubble (clamped to the last
|
|
16
|
+
* `LIVE_STREAM_TAIL_LINES` lines so the region stays short), and
|
|
17
|
+
* - one `⋯ [tool] summary` line per pending tool invocation.
|
|
18
|
+
*
|
|
19
|
+
* Because this region is always at most ~10 + N lines tall, it can never
|
|
20
|
+
* exceed the terminal height → Ink never falls back to clear-screen + full
|
|
21
|
+
* repaint → no flicker, by construction. Finalized messages live in the
|
|
22
|
+
* terminal's native scrollback via `<Static>` (printed exactly once).
|
|
23
|
+
*/
|
|
24
|
+
export function LiveRegion({ live, busy }) {
|
|
25
|
+
const { streaming, runningTools } = live;
|
|
26
|
+
if (!streaming && runningTools.length === 0)
|
|
27
|
+
return null;
|
|
28
|
+
return (React.createElement(Box, { flexDirection: "column", paddingX: 1 },
|
|
29
|
+
streaming && streaming.role === 'assistant' && (React.createElement(StreamingTail, { id: streaming.id, content: streaming.content, ts: streaming.ts, memberName: streaming.memberName, memberId: streaming.memberId })),
|
|
30
|
+
runningTools.map((t) => (React.createElement(Box, { key: t.id, flexDirection: "column" }, renderMessage(t, true)))),
|
|
31
|
+
busy && runningTools.length === 0 && !streaming && (React.createElement(Text, { dimColor: true }, " \u22EF working\u2026"))));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Render the streaming bubble, clamped to the last N content lines. A leading
|
|
35
|
+
* `…` marker is shown when the content was truncated so the user knows there
|
|
36
|
+
* is more above (in the scrollback once finalized). The bubble uses the same
|
|
37
|
+
* header style as `renderMessage` so it is visually continuous when it
|
|
38
|
+
* crosses from live → finalized.
|
|
39
|
+
*/
|
|
40
|
+
function StreamingTail(m) {
|
|
41
|
+
const lines = m.content.split('\n');
|
|
42
|
+
const truncated = lines.length > LIVE_STREAM_TAIL_LINES;
|
|
43
|
+
const tail = truncated ? lines.slice(-LIVE_STREAM_TAIL_LINES) : lines;
|
|
44
|
+
return (React.createElement(Box, { key: m.id, flexDirection: "column", marginBottom: 1 },
|
|
45
|
+
React.createElement(Text, { color: "green", bold: true },
|
|
46
|
+
"\u25C6 assistant",
|
|
47
|
+
m.memberName ? React.createElement(Text, { color: "magenta" },
|
|
48
|
+
" \u00B7 ",
|
|
49
|
+
m.memberName) : null),
|
|
50
|
+
React.createElement(Box, { marginLeft: 2, flexDirection: "column" },
|
|
51
|
+
truncated && React.createElement(Text, { dimColor: true }, "\u2026"),
|
|
52
|
+
React.createElement(Text, null, tail.join('\n')))));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=LiveRegion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LiveRegion.js","sourceRoot":"","sources":["../../../src/cli/components/LiveRegion.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGhD;;;;;GAKG;AACH,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAOlC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,EAAmB;IACxD,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAEzC,IAAI,CAAC,SAAS,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEzD,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC;QACpC,SAAS,IAAI,SAAS,CAAC,IAAI,KAAK,WAAW,IAAI,CAC9C,oBAAC,aAAa,IACZ,EAAE,EAAE,SAAS,CAAC,EAAE,EAChB,OAAO,EAAE,SAAS,CAAC,OAAO,EAC1B,EAAE,EAAE,SAAS,CAAC,EAAE,EAChB,UAAU,EAAE,SAAS,CAAC,UAAU,EAChC,QAAQ,EAAE,SAAS,CAAC,QAAQ,GAC5B,CACH;QACA,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACvB,oBAAC,GAAG,IAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,aAAa,EAAC,QAAQ,IACnC,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CACnB,CACP,CAAC;QACD,IAAI,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,CAClD,oBAAC,IAAI,IAAC,QAAQ,kCAAmB,CAClC,CACG,CACP,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,CAMtB;IACC,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,sBAAsB,CAAC;IACxD,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACtE,OAAO,CACL,oBAAC,GAAG,IAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,aAAa,EAAC,QAAQ,EAAC,YAAY,EAAE,CAAC;QACpD,oBAAC,IAAI,IAAC,KAAK,EAAC,OAAO,EAAC,IAAI;;YAErB,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS;;gBAAK,CAAC,CAAC,UAAU,CAAQ,CAAC,CAAC,CAAC,IAAI,CAChE;QACP,oBAAC,GAAG,IAAC,UAAU,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ;YACvC,SAAS,IAAI,oBAAC,IAAI,IAAC,QAAQ,mBAAS;YACrC,oBAAC,IAAI,QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAQ,CAC1B,CACF,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { memo, useId } from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
/**
|
|
4
4
|
* Sidebar showing: formatted skill list (from formatSkillList), session count,
|
|
5
5
|
* and an indicator when the user is typing a slash command.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* B11 fix — wrapped in React.memo so the sidebar doesn't re-render on every
|
|
8
|
+
* App re-render (e.g. every input keystroke or streaming delta). It only
|
|
9
|
+
* re-renders when its props actually change.
|
|
10
|
+
*
|
|
11
|
+
* B20 fix — width is now a prop, scaled to the terminal columns instead of
|
|
12
|
+
* being hardcoded to 40. On narrow terminals the sidebar shrinks so it
|
|
13
|
+
* doesn't overlap the chat stream.
|
|
13
14
|
*/
|
|
14
|
-
function SidebarImpl({ skillList, sessionCount, isSlashMode, height }) {
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
return (React.createElement(Box, { flexDirection: "column", width:
|
|
15
|
+
function SidebarImpl({ skillList, sessionCount, isSlashMode, height, width }) {
|
|
16
|
+
// Stable per-instance prefix to guarantee unique keys across renders and
|
|
17
|
+
// across sibling Sidebar instances (shouldn't exist today, but defensive).
|
|
18
|
+
const uidPrefix = useId();
|
|
19
|
+
const lines = skillList.split('\n');
|
|
20
|
+
const headerLines = 3 + (isSlashMode ? 1 : 0);
|
|
21
|
+
const maxSkillLines = Math.max(2, height - headerLines - 3); // 3 for border + padding
|
|
22
|
+
const visibleSkillLines = lines.slice(0, maxSkillLines);
|
|
23
|
+
const showTruncatedIndicator = lines.length > maxSkillLines;
|
|
24
|
+
if (showTruncatedIndicator) {
|
|
25
|
+
visibleSkillLines.push(' ... (more in /skills)');
|
|
26
|
+
}
|
|
27
|
+
return (React.createElement(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: "gray", paddingX: 1 },
|
|
27
28
|
React.createElement(Text, { bold: true, color: "cyan" }, "Skills & sessions"),
|
|
28
29
|
React.createElement(Text, { dimColor: true },
|
|
29
30
|
"Sessions: ",
|
|
30
31
|
sessionCount),
|
|
31
32
|
isSlashMode && React.createElement(Text, { color: "yellow" }, "\u2328 slash command mode"),
|
|
32
|
-
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
React.createElement(Box, { marginTop: 1, flexDirection: "column" }, visibleSkillLines.map((line, idx) => (
|
|
34
|
+
// B12 fix — combine React's useId() with idx + line content for a
|
|
35
|
+
// guaranteed-unique key. formatSkillList can legitimately emit
|
|
36
|
+
// multiple blank lines or duplicate entries (e.g. the
|
|
37
|
+
// "... (more in /skills)" footer when the list is short), which
|
|
38
|
+
// would otherwise collide when used as a key directly.
|
|
39
|
+
React.createElement(Text, { key: `${uidPrefix}-${idx}:${line}` }, line))))));
|
|
37
40
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
});
|
|
41
|
+
/**
|
|
42
|
+
* B11 — memoized export. Custom equality is shallow; props are primitives
|
|
43
|
+
* (strings + numbers + booleans) so React's default shallow compare is fine.
|
|
44
|
+
*/
|
|
45
|
+
export const Sidebar = memo(SidebarImpl);
|
|
44
46
|
//# sourceMappingURL=Sidebar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.js","sourceRoot":"","sources":["../../../src/cli/components/Sidebar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"Sidebar.js","sourceRoot":"","sources":["../../../src/cli/components/Sidebar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAUhC;;;;;;;;;;;GAWG;AACH,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAgB;IACxF,yEAAyE;IACzE,2EAA2E;IAC3E,MAAM,SAAS,GAAG,KAAK,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,yBAAyB;IACtF,MAAM,iBAAiB,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;IACxD,MAAM,sBAAsB,GAAG,KAAK,CAAC,MAAM,GAAG,aAAa,CAAC;IAC5D,IAAI,sBAAsB,EAAE,CAAC;QAC3B,iBAAiB,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAC,QAAQ,EAAC,WAAW,EAAC,MAAM,EAAC,QAAQ,EAAE,CAAC;QAC3G,oBAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,MAAM,wBAA6B;QACpD,oBAAC,IAAI,IAAC,QAAQ;;YAAY,YAAY,CAAQ;QAC7C,WAAW,IAAI,oBAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,gCAA4B;QAChE,oBAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ,IACtC,iBAAiB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;QACpC,kEAAkE;QAClE,+DAA+D;QAC/D,sDAAsD;QACtD,gEAAgE;QAChE,uDAAuD;QACvD,oBAAC,IAAI,IAAC,GAAG,EAAE,GAAG,SAAS,IAAI,GAAG,IAAI,IAAI,EAAE,IAAG,IAAI,CAAQ,CACxD,CAAC,CACE,CACF,CACP,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { formatCost, formatTokens } from '../modelPricing.js';
|
|
4
|
+
/**
|
|
5
|
+
* StatusBar — single one-line status bar replacing the persistent v0.6
|
|
6
|
+
* `Header`. It lives in the dynamic region (repainted by Ink) and condenses:
|
|
7
|
+
* provider · model · session · tokens · cost · queue · busy.
|
|
8
|
+
*
|
|
9
|
+
* Why one line: the static-scrollback model (v0.7.0) needs the dynamic
|
|
10
|
+
* region as short as possible. A single status line + the input bar + the
|
|
11
|
+
* streaming tail is always well under a screen, so no full repaint.
|
|
12
|
+
*/
|
|
13
|
+
export function StatusBar({ model, provider, sessionId, sessionActive, totalTokens, totalCostUsd, queueCount = 0, busy = false, }) {
|
|
14
|
+
const hasCost = typeof totalCostUsd === 'number' && totalCostUsd > 0;
|
|
15
|
+
const costStr = hasCost ? formatCost(totalCostUsd) : '$0.0000';
|
|
16
|
+
const tokStr = formatTokens(totalTokens ?? 0);
|
|
17
|
+
return (React.createElement(Box, { paddingX: 1 },
|
|
18
|
+
React.createElement(Text, { color: sessionActive ? 'green' : 'gray' }, sessionActive ? '●' : '○'),
|
|
19
|
+
React.createElement(Text, { dimColor: true }, " "),
|
|
20
|
+
React.createElement(Text, { bold: true, color: "cyan" }, provider),
|
|
21
|
+
React.createElement(Text, { dimColor: true }, " \u00B7 "),
|
|
22
|
+
React.createElement(Text, null, model),
|
|
23
|
+
React.createElement(Text, { dimColor: true }, " \u00B7 "),
|
|
24
|
+
React.createElement(Text, { dimColor: true },
|
|
25
|
+
"session ",
|
|
26
|
+
sessionId),
|
|
27
|
+
React.createElement(Text, { dimColor: true }, " \u00B7 "),
|
|
28
|
+
React.createElement(Text, { color: "gray" },
|
|
29
|
+
tokStr,
|
|
30
|
+
" tok"),
|
|
31
|
+
React.createElement(Text, { dimColor: true }, " \u00B7 "),
|
|
32
|
+
React.createElement(Text, { color: hasCost ? 'yellow' : 'gray' }, costStr),
|
|
33
|
+
queueCount > 0 ? (React.createElement(React.Fragment, null,
|
|
34
|
+
React.createElement(Text, { dimColor: true }, " \u00B7 "),
|
|
35
|
+
React.createElement(Text, { color: "magenta" },
|
|
36
|
+
"queue ",
|
|
37
|
+
queueCount))) : null,
|
|
38
|
+
busy ? (React.createElement(React.Fragment, null,
|
|
39
|
+
React.createElement(Text, { dimColor: true }, " \u00B7 "),
|
|
40
|
+
React.createElement(Text, { color: "yellow" }, "working"))) : null));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=StatusBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusBar.js","sourceRoot":"","sources":["../../../src/cli/components/StatusBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAa9D;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,KAAK,EACL,QAAQ,EACR,SAAS,EACT,aAAa,EACb,WAAW,EACX,YAAY,EACZ,UAAU,GAAG,CAAC,EACd,IAAI,GAAG,KAAK,GACG;IACf,MAAM,OAAO,GAAG,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,GAAG,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC;IAC9C,OAAO,CACL,oBAAC,GAAG,IAAC,QAAQ,EAAE,CAAC;QACd,oBAAC,IAAI,IAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,IAC1C,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CACrB;QACP,oBAAC,IAAI,IAAC,QAAQ,cAAS;QACvB,oBAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,MAAM,IAAE,QAAQ,CAAQ;QACzC,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,QAAE,KAAK,CAAQ;QACpB,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,IAAC,QAAQ;;YAAU,SAAS,CAAQ;QACzC,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,IAAC,KAAK,EAAC,MAAM;YAAE,MAAM;mBAAY;QACtC,oBAAC,IAAI,IAAC,QAAQ,qBAAW;QACzB,oBAAC,IAAI,IAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAG,OAAO,CAAQ;QACzD,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAChB;YACE,oBAAC,IAAI,IAAC,QAAQ,qBAAW;YACzB,oBAAC,IAAI,IAAC,KAAK,EAAC,SAAS;;gBAAQ,UAAU,CAAQ,CAC9C,CACJ,CAAC,CAAC,CAAC,IAAI;QACP,IAAI,CAAC,CAAC,CAAC,CACN;YACE,oBAAC,IAAI,IAAC,QAAQ,qBAAW;YACzB,oBAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,cAAe,CAClC,CACJ,CAAC,CAAC,CAAC,IAAI,CACJ,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { useStdout } from 'ink';
|
|
4
|
+
import { formatToolResult } from './toolFormat.js';
|
|
5
|
+
function borderColor(toolName, ok) {
|
|
6
|
+
if (ok === false)
|
|
7
|
+
return 'red';
|
|
8
|
+
const lower = toolName.toLowerCase();
|
|
9
|
+
if (lower.includes('read') || lower === 'cat' || lower.includes('grep') || lower.includes('search'))
|
|
10
|
+
return 'green';
|
|
11
|
+
if (lower.includes('write') || lower.includes('edit'))
|
|
12
|
+
return 'yellow';
|
|
13
|
+
if (lower === 'bash' || lower === 'shell' || lower === 'exec')
|
|
14
|
+
return 'red';
|
|
15
|
+
return 'cyan';
|
|
16
|
+
}
|
|
17
|
+
function ToolOutputImpl(props) {
|
|
18
|
+
const { toolName, summary, body, ok, durationMs, live = false } = props;
|
|
19
|
+
const color = borderColor(toolName, ok);
|
|
20
|
+
const { stdout } = useStdout();
|
|
21
|
+
// B3: clamp bordered box width so boxes don't stretch to full terminal and
|
|
22
|
+
// produce the mixed-width wall seen in v0.7.0.
|
|
23
|
+
const termWidth = stdout?.columns ?? 80;
|
|
24
|
+
const boxWidth = Math.min(Math.max(40, termWidth - 6), 100);
|
|
25
|
+
// Pending (live region): single line, no body, no duration.
|
|
26
|
+
if (live || ok === undefined) {
|
|
27
|
+
const summaryLine = ['⋯', `[${toolName}]`, summary].filter(Boolean).join(' ');
|
|
28
|
+
return (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
29
|
+
React.createElement(Box, null,
|
|
30
|
+
React.createElement(Text, { color: color }, summaryLine))));
|
|
31
|
+
}
|
|
32
|
+
// Finalized — format the body per tool policy (B1).
|
|
33
|
+
const isError = ok === false;
|
|
34
|
+
const formatted = formatToolResult(toolName, body);
|
|
35
|
+
const status = ok ? '✓' : '✗';
|
|
36
|
+
const summaryLine = [
|
|
37
|
+
status,
|
|
38
|
+
`[${toolName}]`,
|
|
39
|
+
summary,
|
|
40
|
+
durationMs !== undefined ? `(${durationMs}ms)` : '',
|
|
41
|
+
].filter(Boolean).join(' ');
|
|
42
|
+
// B3: one-line results (write_file/edit_file success) print inline — no box.
|
|
43
|
+
if (formatted.oneLine && !isError) {
|
|
44
|
+
const inline = formatted.lines[0] ?? '';
|
|
45
|
+
return (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
46
|
+
React.createElement(Box, null,
|
|
47
|
+
React.createElement(Text, { color: color }, summaryLine)),
|
|
48
|
+
React.createElement(Box, { marginLeft: 2 },
|
|
49
|
+
React.createElement(Text, { dimColor: true }, inline))));
|
|
50
|
+
}
|
|
51
|
+
const bodyText = formatted.lines.join('\n');
|
|
52
|
+
const metaSuffix = formatted.meta ? `\n${formatted.meta}` : '';
|
|
53
|
+
return (React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
54
|
+
React.createElement(Box, null,
|
|
55
|
+
React.createElement(Text, { color: color }, summaryLine)),
|
|
56
|
+
React.createElement(Box, { flexDirection: "column", marginLeft: 2, width: boxWidth, borderStyle: "single", borderColor: color, paddingX: 1 },
|
|
57
|
+
React.createElement(Text, { dimColor: isError },
|
|
58
|
+
bodyText,
|
|
59
|
+
metaSuffix))));
|
|
60
|
+
}
|
|
61
|
+
export const ToolOutput = React.memo(ToolOutputImpl);
|
|
62
|
+
/**
|
|
63
|
+
* Pure helper: classify tool color (exported for unit tests).
|
|
64
|
+
*/
|
|
65
|
+
export function classifyToolColor(toolName, ok) {
|
|
66
|
+
return borderColor(toolName, ok);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=ToolOutput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ToolOutput.js","sourceRoot":"","sources":["../../../src/cli/components/ToolOutput.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toolFormat.ts — v0.7.1 pure formatters for tool rendering (plan B1+B2+B3).
|
|
3
|
+
*
|
|
4
|
+
* The v0.6/v0.7.0 renderer dumped the raw JSON result envelope into a bordered
|
|
5
|
+
* box: escaped `\n`, full-terminal-width borders with inconsistent widths per
|
|
6
|
+
* box, and summary lines that were raw JSON args truncated mid-string. These
|
|
7
|
+
* pure functions replace that with human-readable output keyed by tool name.
|
|
8
|
+
*
|
|
9
|
+
* Two formatters:
|
|
10
|
+
* - {@link formatToolSummary}: the one-line summary (replaces raw-JSON args).
|
|
11
|
+
* - {@link formatToolResult}: the multi-line body (replaces the raw envelope).
|
|
12
|
+
*
|
|
13
|
+
* Both are pure (no React, no Ink) so they are trivially unit-testable and
|
|
14
|
+
* usable by any renderer (the current ToolOutput, the Static path, /last-tool).
|
|
15
|
+
*
|
|
16
|
+
* Truncation is line-based (default 8, `ZELARI_TOOL_OUTPUT_LINES`) with a
|
|
17
|
+
* `… (+K lines)` tail, replacing the 600-char mid-string cut that split JSON
|
|
18
|
+
* strings awkwardly.
|
|
19
|
+
*/
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
/** Default line cap for the printed result body. */
|
|
22
|
+
const DEFAULT_TOOL_OUTPUT_LINES = 8;
|
|
23
|
+
function toolOutputLineCap() {
|
|
24
|
+
const raw = process.env.ZELARI_TOOL_OUTPUT_LINES;
|
|
25
|
+
const n = raw ? Number.parseInt(raw, 10) : DEFAULT_TOOL_OUTPUT_LINES;
|
|
26
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_TOOL_OUTPUT_LINES;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort JSON parse. Returns the parsed value or `null` (caller falls
|
|
30
|
+
* back to treating the input as a plain string).
|
|
31
|
+
*/
|
|
32
|
+
function tryParseJson(s) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(s);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Truncate an array of lines to the cap, appending a `… (+K lines)` marker. */
|
|
41
|
+
function truncateLines(lines) {
|
|
42
|
+
const cap = toolOutputLineCap();
|
|
43
|
+
if (lines.length <= cap)
|
|
44
|
+
return lines;
|
|
45
|
+
return [...lines.slice(0, cap), `… (+${lines.length - cap} lines)`];
|
|
46
|
+
}
|
|
47
|
+
/** Human-readable byte size (KB / MB). */
|
|
48
|
+
function formatBytes(n) {
|
|
49
|
+
if (n >= 1024 * 1024)
|
|
50
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
51
|
+
if (n >= 1024)
|
|
52
|
+
return `${(n / 1024).toFixed(1)} KB`;
|
|
53
|
+
return `${n} B`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Format the body of a tool result for display.
|
|
57
|
+
*
|
|
58
|
+
* @param toolName tool name (determines the per-tool shape handling)
|
|
59
|
+
* @param resultStr the raw result string (JSON envelope from the harness, or
|
|
60
|
+
* a plain string for non-JSON tools / errors)
|
|
61
|
+
*/
|
|
62
|
+
export function formatToolResult(toolName, resultStr) {
|
|
63
|
+
const lower = toolName.toLowerCase();
|
|
64
|
+
// bash / shell — envelope: { stdout, stderr, exitCode, hint? }
|
|
65
|
+
if (lower === 'bash' || lower === 'shell' || lower === 'exec') {
|
|
66
|
+
const parsed = tryParseJson(resultStr);
|
|
67
|
+
if (parsed && typeof parsed === 'object') {
|
|
68
|
+
const stdout = parsed.stdout ?? '';
|
|
69
|
+
const lines = stdout.length > 0 ? stdout.replace(/\r\n/g, '\n').split('\n') : [];
|
|
70
|
+
const metaParts = [];
|
|
71
|
+
if (parsed.stderr && parsed.stderr.trim().length > 0) {
|
|
72
|
+
metaParts.push(`stderr: ${parsed.stderr.trim().split('\n')[0]}`);
|
|
73
|
+
}
|
|
74
|
+
if (typeof parsed.exitCode === 'number' && parsed.exitCode !== 0) {
|
|
75
|
+
metaParts.push(`exit ${parsed.exitCode}`);
|
|
76
|
+
}
|
|
77
|
+
// v0.7.3: interactive-prompt detection (see shell.ts INTERACTIVE_HINT).
|
|
78
|
+
if (typeof parsed.hint === 'string' && parsed.hint.length > 0) {
|
|
79
|
+
metaParts.push('⚠ interactive prompt cancelled — needs non-interactive flags or manual file creation');
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
lines: truncateLines(lines),
|
|
83
|
+
meta: metaParts.length > 0 ? metaParts.join(' · ') : undefined,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// read_file — envelope: { path, content, ... }
|
|
88
|
+
if (lower.includes('read') || lower === 'cat') {
|
|
89
|
+
const parsed = tryParseJson(resultStr);
|
|
90
|
+
if (parsed && typeof parsed === 'object' && typeof parsed.content === 'string') {
|
|
91
|
+
return { lines: truncateLines(parsed.content.replace(/\r\n/g, '\n').split('\n')) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// write_file / edit_file — success is a single line, no box (B3).
|
|
95
|
+
if (lower.includes('write') || lower.includes('edit')) {
|
|
96
|
+
const parsed = tryParseJson(resultStr);
|
|
97
|
+
if (parsed && typeof parsed === 'object') {
|
|
98
|
+
const p = parsed.path ? rel(parsed.path) : '';
|
|
99
|
+
if (typeof parsed.bytesWritten === 'number') {
|
|
100
|
+
return { lines: [`wrote ${formatBytes(parsed.bytesWritten)} → ${p}`], oneLine: true };
|
|
101
|
+
}
|
|
102
|
+
if (typeof parsed.occurrencesReplaced === 'number') {
|
|
103
|
+
return {
|
|
104
|
+
lines: [`replaced ${parsed.occurrencesReplaced} occurrence(s) in ${p}`],
|
|
105
|
+
oneLine: true,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// list_files — envelope: { dir, entries: [{name, type}], truncated }
|
|
111
|
+
if (lower.includes('list') || lower.includes('ls')) {
|
|
112
|
+
const parsed = tryParseJson(resultStr);
|
|
113
|
+
if (parsed && Array.isArray(parsed.entries)) {
|
|
114
|
+
const names = parsed.entries.slice(0, 10).map((e) => e.name);
|
|
115
|
+
const head = truncateLines(names);
|
|
116
|
+
const count = parsed.entries.length;
|
|
117
|
+
const dir = parsed.dir ? rel(parsed.dir) : '';
|
|
118
|
+
const metaParts = [];
|
|
119
|
+
if (dir)
|
|
120
|
+
metaParts.push(`${count} entries in ${dir}`);
|
|
121
|
+
else
|
|
122
|
+
metaParts.push(`${count} entries`);
|
|
123
|
+
if (parsed.truncated)
|
|
124
|
+
metaParts.push(`(truncated)`);
|
|
125
|
+
return { lines: head, meta: metaParts.join(' ') };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// grep_content / search — envelope: { matches/results: [...] }
|
|
129
|
+
if (lower.includes('grep') || lower.includes('search')) {
|
|
130
|
+
const parsed = tryParseJson(resultStr);
|
|
131
|
+
const arr = parsed?.matches ?? parsed?.results;
|
|
132
|
+
if (Array.isArray(arr)) {
|
|
133
|
+
return {
|
|
134
|
+
lines: truncateLines(arr.map((m) => (typeof m === 'string' ? m : JSON.stringify(m)))),
|
|
135
|
+
meta: `${arr.length} match(es)`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Fallback: treat as plain text (split on real newlines; if it's JSON we
|
|
140
|
+
// already failed to match a known shape, so show it verbatim).
|
|
141
|
+
return { lines: truncateLines(resultStr.replace(/\r\n/g, '\n').split('\n')) };
|
|
142
|
+
}
|
|
143
|
+
/** Make a path relative to cwd when possible (shorter display). */
|
|
144
|
+
function rel(p) {
|
|
145
|
+
try {
|
|
146
|
+
const r = path.relative(process.cwd(), p);
|
|
147
|
+
return r && !r.startsWith('..') ? r : p;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return p;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Format the one-line summary for a tool invocation (replaces raw-JSON args).
|
|
155
|
+
*
|
|
156
|
+
* @param toolName tool name
|
|
157
|
+
* @param args the args object/value as received from the model
|
|
158
|
+
* @param maxWidth optional column cap; the summary is truncated (with `…`) to
|
|
159
|
+
* fit so it never wraps mid-token. When omitted, a sane default
|
|
160
|
+
* is applied.
|
|
161
|
+
*/
|
|
162
|
+
export function formatToolSummary(toolName, args, maxWidth) {
|
|
163
|
+
const cap = maxWidth && maxWidth > 10 ? maxWidth : 100;
|
|
164
|
+
const lower = toolName.toLowerCase();
|
|
165
|
+
const a = (args && typeof args === 'object' ? args : {});
|
|
166
|
+
let summary;
|
|
167
|
+
if (lower === 'bash' || lower === 'shell' || lower === 'exec') {
|
|
168
|
+
summary = String(a['command'] ?? a['cmd'] ?? '');
|
|
169
|
+
}
|
|
170
|
+
else if (lower.includes('read') || lower === 'cat') {
|
|
171
|
+
summary = typeof a['path'] === 'string' ? rel(a['path']) : '';
|
|
172
|
+
}
|
|
173
|
+
else if (lower.includes('write') || lower.includes('edit')) {
|
|
174
|
+
const p = typeof a['path'] === 'string' ? rel(a['path']) : '';
|
|
175
|
+
summary = p;
|
|
176
|
+
}
|
|
177
|
+
else if (lower.includes('list') || lower.includes('ls')) {
|
|
178
|
+
const dir = typeof a['dir'] === 'string' ? rel(a['dir']) : '.';
|
|
179
|
+
const depth = typeof a['maxDepth'] === 'number' ? ` (depth ${a['maxDepth']})` : '';
|
|
180
|
+
summary = `${dir}${depth}`;
|
|
181
|
+
}
|
|
182
|
+
else if (lower.includes('grep') || lower.includes('search')) {
|
|
183
|
+
const pattern = String(a['pattern'] ?? a['query'] ?? '');
|
|
184
|
+
const p = typeof a['path'] === 'string' ? ` in ${rel(a['path'])}` : '';
|
|
185
|
+
summary = `${pattern}${p}`;
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Fallback: compact JSON, but truncated at the column cap (not mid-string
|
|
189
|
+
// like the old slice(0,120) which cut content values arbitrarily).
|
|
190
|
+
const json = JSON.stringify(args) ?? '';
|
|
191
|
+
summary = json;
|
|
192
|
+
}
|
|
193
|
+
return summary.length > cap ? `${summary.slice(0, cap - 1)}…` : summary;
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=toolFormat.js.map
|