flowmind 1.5.6 → 1.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,9 +1,16 @@
1
1
  # Changelog
2
2
 
3
- ## [1.5.6] - 2026-07-02
3
+ ## [1.5.8] - 2026-07-02
4
+
5
+ ### Fixed
6
+ - Log audit no longer asks for a time range when the user already specified a relative date like "today"
7
+ - Aliyun SLS log query timestamps are normalized to integer seconds before the MCP call, matching the backend parameter contract
8
+
9
+ ## [1.5.7] - 2026-07-02
4
10
 
5
11
  ### Added
6
12
  - Added a `check-update` script and CLI command so already installed users can quickly see whether a newer npm release is available
13
+ - TUI now checks npm for a newer release after startup and shows a non-blocking update reminder banner when one is available
7
14
 
8
15
  ### Fixed
9
16
  - TUI and dashboard now degrade gracefully on narrow or very small terminals instead of crashing on resize
@@ -68,12 +68,47 @@ class AliyunSlsAdapter extends LogServiceAdapter {
68
68
  project: project || this.config.config?.defaultProject,
69
69
  logstore: logstore || this.config.config?.defaultLogstore,
70
70
  query: query || '',
71
- from,
72
- to,
71
+ from: normalizeTimestamp(from),
72
+ to: normalizeTimestamp(to),
73
73
  line,
74
74
  reverse: true
75
75
  };
76
76
  }
77
77
  }
78
78
 
79
+ function normalizeTimestamp(value) {
80
+ if (value === null || value === undefined || value === '') {
81
+ return undefined;
82
+ }
83
+
84
+ if (typeof value === 'number' && Number.isFinite(value)) {
85
+ return normalizeEpochLikeNumber(value);
86
+ }
87
+
88
+ const numeric = Number(value);
89
+ if (Number.isFinite(numeric) && String(value).trim() !== '') {
90
+ return normalizeEpochLikeNumber(numeric);
91
+ }
92
+
93
+ const parsed = Date.parse(value);
94
+ if (Number.isFinite(parsed)) {
95
+ return Math.trunc(parsed / 1000);
96
+ }
97
+
98
+ return undefined;
99
+ }
100
+
101
+ function normalizeEpochLikeNumber(value) {
102
+ const integer = Math.trunc(value);
103
+ if (!Number.isFinite(integer)) {
104
+ return undefined;
105
+ }
106
+
107
+ if (Math.abs(integer) >= 1e11) {
108
+ return Math.trunc(integer / 1000);
109
+ }
110
+
111
+ return integer;
112
+ }
113
+
79
114
  module.exports = AliyunSlsAdapter;
@@ -1,4 +1,5 @@
1
- const { execSync } = require('child_process');
1
+ const { execFile, execSync } = require('child_process');
2
+ const { promisify } = require('util');
2
3
 
3
4
  function compareVersions(currentVersion, latestVersion) {
4
5
  const current = parseVersion(currentVersion);
@@ -23,6 +24,16 @@ function getLatestVersion(packageName = 'flowmind') {
23
24
  return execSync(`npm view ${packageName} version`, { encoding: 'utf-8' }).trim();
24
25
  }
25
26
 
27
+ async function getLatestVersionAsync(packageName = 'flowmind') {
28
+ const run = promisify(execFile);
29
+ const result = await run('npm', ['view', packageName, 'version'], {
30
+ encoding: 'utf-8',
31
+ timeout: 2500,
32
+ maxBuffer: 1024 * 64
33
+ });
34
+ return String(result).trim();
35
+ }
36
+
26
37
  function isGlobalInstall(packageJsonPath, packageName = 'flowmind') {
27
38
  try {
28
39
  const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
@@ -65,10 +76,52 @@ function getUpdateStatus({
65
76
  };
66
77
  }
67
78
 
79
+ async function getUpdateStatusAsync({
80
+ packageName = 'flowmind',
81
+ currentVersion = '0.0.0',
82
+ packageJsonPath = ''
83
+ } = {}) {
84
+ const [latestVersion, globalInstall] = await Promise.all([
85
+ getLatestVersionAsync(packageName),
86
+ isGlobalInstallAsync(packageJsonPath, packageName)
87
+ ]);
88
+ const comparison = compareVersions(currentVersion, latestVersion);
89
+
90
+ return {
91
+ packageName,
92
+ currentVersion,
93
+ latestVersion,
94
+ comparison,
95
+ globalInstall,
96
+ installCommand: buildInstallCommand({
97
+ packageName,
98
+ version: latestVersion,
99
+ globalInstall
100
+ })
101
+ };
102
+ }
103
+
104
+ async function isGlobalInstallAsync(packageJsonPath, packageName = 'flowmind') {
105
+ try {
106
+ const run = promisify(execFile);
107
+ const result = await run('npm', ['root', '-g'], {
108
+ encoding: 'utf-8',
109
+ timeout: 2500,
110
+ maxBuffer: 1024 * 16
111
+ });
112
+ const globalRoot = String(result).trim();
113
+ return String(packageJsonPath || '').startsWith(globalRoot) || String(packageJsonPath || '').includes(`${globalRoot}/${packageName}`);
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+
68
119
  module.exports = {
69
120
  buildInstallCommand,
70
121
  compareVersions,
71
122
  getLatestVersion,
123
+ getLatestVersionAsync,
72
124
  getUpdateStatus,
125
+ getUpdateStatusAsync,
73
126
  isGlobalInstall
74
127
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowmind",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "description": "Memory and workflow automation for MCP, Codex, and Claude Code. Reuse repeatable developer operations through skills and explicit feedback.",
5
5
  "main": "core/index.js",
6
6
  "bin": {
@@ -182,10 +182,41 @@ function buildNoRecordsMessage(params) {
182
182
  }
183
183
 
184
184
  if (params.keyword) {
185
- return `未找到符合關鍵字:${params.keyword} 的日誌。請補充服務或時間範圍。`;
185
+ const suggestions = [];
186
+ if (!params.service) {
187
+ suggestions.push('服務');
188
+ }
189
+ if (!params.timeRange) {
190
+ suggestions.push('時間範圍');
191
+ }
192
+ suggestions.push('放寬關鍵字');
193
+
194
+ const suggestionText = suggestions.length > 0
195
+ ? `請嘗試${joinChineseList(suggestions)}。`
196
+ : '請嘗試放寬關鍵字。';
197
+
198
+ return `未找到符合關鍵字:${params.keyword} 的日誌。${suggestionText}`;
186
199
  }
187
200
 
188
- return '未找到符合條件的日誌。請補充關鍵字、服務或時間範圍。';
201
+ const suggestions = [];
202
+ if (!params.service) suggestions.push('服務');
203
+ if (!params.keyword) suggestions.push('關鍵字');
204
+ if (!params.timeRange) suggestions.push('時間範圍');
205
+
206
+ return suggestions.length > 0
207
+ ? `未找到符合條件的日誌。請補充${joinChineseList(suggestions)}。`
208
+ : '未找到符合條件的日誌。';
209
+ }
210
+
211
+ function joinChineseList(items) {
212
+ const list = (items || []).filter(Boolean);
213
+ if (list.length <= 1) {
214
+ return list[0] || '';
215
+ }
216
+ if (list.length === 2) {
217
+ return `${list[0]}或${list[1]}`;
218
+ }
219
+ return `${list.slice(0, -1).join('、')}或${list[list.length - 1]}`;
189
220
  }
190
221
 
191
222
  function summarizeRawExecution(execution, data) {
package/tui/app.jsx CHANGED
@@ -5,6 +5,7 @@ const ChatPanel = require('./components/ChatPanel.jsx');
5
5
  const StatusBar = require('./components/StatusBar.jsx');
6
6
  const { formatResultText } = require('./format-result');
7
7
  const { getTuiLayout, MIN_COLUMNS, MIN_ROWS } = require('./layout');
8
+ const { getUpdateStatusAsync } = require('../core/update-notifier');
8
9
 
9
10
  class TuiErrorBoundary extends React.Component {
10
11
  constructor(props) {
@@ -41,6 +42,7 @@ function App({ flowmind, asciiMode = false }) {
41
42
  const MAX_MESSAGES = 60;
42
43
  const [messages, setMessages] = React.useState([]);
43
44
  const [isProcessing, setIsProcessing] = React.useState(false);
45
+ const [updateNotice, setUpdateNotice] = React.useState(null);
44
46
  const [focusPanel, setFocusPanel] = React.useState('chat'); // 'chat' | 'sidebar'
45
47
  const [terminalSize, setTerminalSize] = React.useState(() => ({
46
48
  columns: Number(process.stdout?.columns) || 0,
@@ -56,6 +58,39 @@ function App({ flowmind, asciiMode = false }) {
56
58
  return () => { mountedRef.current = false; };
57
59
  }, []);
58
60
 
61
+ React.useEffect(() => {
62
+ let cancelled = false;
63
+
64
+ async function checkForUpdates() {
65
+ try {
66
+ const status = await getUpdateStatusAsync({
67
+ packageName: 'flowmind',
68
+ currentVersion: require('../package.json').version,
69
+ packageJsonPath: require.resolve('../package.json')
70
+ });
71
+
72
+ if (cancelled || !mountedRef.current) return;
73
+ if (status.comparison < 0) {
74
+ setUpdateNotice({
75
+ currentVersion: status.currentVersion,
76
+ latestVersion: status.latestVersion,
77
+ installCommand: status.installCommand
78
+ });
79
+ }
80
+ } catch {
81
+ if (!cancelled && mountedRef.current) {
82
+ setUpdateNotice(null);
83
+ }
84
+ }
85
+ }
86
+
87
+ checkForUpdates();
88
+
89
+ return () => {
90
+ cancelled = true;
91
+ };
92
+ }, []);
93
+
59
94
  React.useEffect(() => {
60
95
  const updateSize = () => {
61
96
  if (!mountedRef.current) return;
@@ -149,6 +184,15 @@ function App({ flowmind, asciiMode = false }) {
149
184
  return (
150
185
  React.createElement(TuiErrorBoundary, null,
151
186
  React.createElement(Box, { flexDirection: 'column', width: '100%', height: '100%' },
187
+ updateNotice && React.createElement(Box, {
188
+ borderStyle: 'single',
189
+ borderColor: 'yellow',
190
+ paddingX: 1,
191
+ marginBottom: 1
192
+ },
193
+ React.createElement(Text, { color: 'yellow' }, `Update available: ${updateNotice.currentVersion} → ${updateNotice.latestVersion}`),
194
+ React.createElement(Text, { color: 'gray' }, ` Run ${updateNotice.installCommand}`)
195
+ ),
152
196
  React.createElement(Box, { flexDirection: 'row', flexGrow: 1 },
153
197
  React.createElement(Sidebar, { flowmind: flowmind, width: layout.sidebarWidth, onSkillSelect: handleSkillSelect, focused: focusPanel === 'sidebar', asciiMode: asciiMode }),
154
198
  React.createElement(ChatPanel, {