chainlesschain 0.45.0 → 0.45.2
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/src/commands/update.js +31 -0
- package/src/lib/version-checker.js +55 -27
- package/src/lib/web-ui-server.js +63 -41
package/package.json
CHANGED
package/src/commands/update.js
CHANGED
|
@@ -94,6 +94,37 @@ export function registerUpdateCommand(program) {
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// When update source is npm-only (GitHub Release CI still building),
|
|
98
|
+
// skip desktop app download and only update the CLI package
|
|
99
|
+
if (result.source === "npm") {
|
|
100
|
+
logger.info(
|
|
101
|
+
`GitHub Release for v${result.latestVersion} is not yet available (CI may still be building).`,
|
|
102
|
+
);
|
|
103
|
+
logger.info(`Updating CLI package from npm...`);
|
|
104
|
+
|
|
105
|
+
const doUpdate = await askConfirm(
|
|
106
|
+
`Update CLI to v${result.latestVersion} via npm?`,
|
|
107
|
+
true,
|
|
108
|
+
);
|
|
109
|
+
if (!doUpdate) {
|
|
110
|
+
logger.info("Update cancelled");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const cliUpdated = await selfUpdateCli(result.latestVersion);
|
|
115
|
+
if (cliUpdated) {
|
|
116
|
+
logger.success(`CLI updated to v${result.latestVersion}`);
|
|
117
|
+
logger.info(
|
|
118
|
+
`Desktop app update will be available once GitHub Release CI completes.`,
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
logger.warn(
|
|
122
|
+
`CLI self-update failed. Please run manually:\n npm install -g chainlesschain@${result.latestVersion}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
97
128
|
const doUpdate = await askConfirm(
|
|
98
129
|
`Download v${result.latestVersion}?`,
|
|
99
130
|
true,
|
|
@@ -2,48 +2,76 @@ import semver from "semver";
|
|
|
2
2
|
import { GITHUB_RELEASES_URL, VERSION } from "../constants.js";
|
|
3
3
|
import logger from "./logger.js";
|
|
4
4
|
|
|
5
|
+
const NPM_REGISTRY_URL = "https://registry.npmjs.org/chainlesschain/latest";
|
|
6
|
+
|
|
5
7
|
export async function checkForUpdates(options = {}) {
|
|
6
8
|
const channel = options.channel || "stable";
|
|
7
9
|
const currentVersion = options.currentVersion || VERSION;
|
|
8
10
|
|
|
11
|
+
// Try GitHub releases first (has full release notes and assets)
|
|
9
12
|
try {
|
|
10
13
|
const releases = await fetchReleases();
|
|
11
14
|
const filtered = filterByChannel(releases, channel);
|
|
12
15
|
|
|
13
|
-
if (filtered.length
|
|
16
|
+
if (filtered.length > 0) {
|
|
17
|
+
const latest = filtered[0];
|
|
18
|
+
const latestVersion = latest.tag_name.replace(/^v/, "");
|
|
19
|
+
const updateAvailable = semver.gt(latestVersion, currentVersion);
|
|
20
|
+
|
|
14
21
|
return {
|
|
15
|
-
updateAvailable
|
|
22
|
+
updateAvailable,
|
|
16
23
|
currentVersion,
|
|
17
|
-
latestVersion
|
|
24
|
+
latestVersion,
|
|
25
|
+
releaseUrl: latest.html_url,
|
|
26
|
+
publishedAt: latest.published_at,
|
|
27
|
+
releaseNotes: latest.body,
|
|
28
|
+
assets: latest.assets.map((a) => ({
|
|
29
|
+
name: a.name,
|
|
30
|
+
size: a.size,
|
|
31
|
+
downloadUrl: a.browser_download_url,
|
|
32
|
+
})),
|
|
18
33
|
};
|
|
19
34
|
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
logger.verbose(`GitHub release check failed: ${err.message}`);
|
|
37
|
+
}
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
updateAvailable,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
downloadUrl: a.browser_download_url,
|
|
36
|
-
})),
|
|
37
|
-
};
|
|
39
|
+
// Fallback: check npm registry for CLI package version
|
|
40
|
+
// This catches cases where the GitHub Release CI is still building but npm is already published
|
|
41
|
+
try {
|
|
42
|
+
const npmVersion = await fetchNpmVersion();
|
|
43
|
+
if (npmVersion) {
|
|
44
|
+
const updateAvailable = semver.gt(npmVersion, currentVersion);
|
|
45
|
+
return {
|
|
46
|
+
updateAvailable,
|
|
47
|
+
currentVersion,
|
|
48
|
+
latestVersion: npmVersion,
|
|
49
|
+
releaseUrl: `https://www.npmjs.com/package/chainlesschain/v/${npmVersion}`,
|
|
50
|
+
source: "npm",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
38
53
|
} catch (err) {
|
|
39
|
-
logger.verbose(`
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
logger.verbose(`npm registry check failed: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
updateAvailable: false,
|
|
59
|
+
currentVersion,
|
|
60
|
+
latestVersion: currentVersion,
|
|
61
|
+
error:
|
|
62
|
+
"Unable to check for updates (GitHub releases and npm registry both unavailable)",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function fetchNpmVersion() {
|
|
67
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
68
|
+
headers: { Accept: "application/json" },
|
|
69
|
+
});
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new Error(`npm registry error: HTTP ${response.status}`);
|
|
46
72
|
}
|
|
73
|
+
const data = await response.json();
|
|
74
|
+
return data.version || null;
|
|
47
75
|
}
|
|
48
76
|
|
|
49
77
|
async function fetchReleases() {
|
package/src/lib/web-ui-server.js
CHANGED
|
@@ -460,7 +460,7 @@ function buildHtml({
|
|
|
460
460
|
<div id="conn-dot"></div>
|
|
461
461
|
<span id="conn-label">未连接</span>
|
|
462
462
|
</div>
|
|
463
|
-
<span id="version-label">v5.0.2.
|
|
463
|
+
<span id="version-label">v5.0.2.3</span>
|
|
464
464
|
</div>
|
|
465
465
|
</nav>
|
|
466
466
|
|
|
@@ -522,6 +522,7 @@ function buildHtml({
|
|
|
522
522
|
let streamBuffer = '';
|
|
523
523
|
let selectedSessionType = 'agent';
|
|
524
524
|
let pendingQuestionResolve = null;
|
|
525
|
+
let _msgId = 0;
|
|
525
526
|
const sessions = new Map(); // id → { id, title, type, createdAt }
|
|
526
527
|
|
|
527
528
|
// ── DOM refs ─────────────────────────────────────────────────────────────
|
|
@@ -611,6 +612,7 @@ function buildHtml({
|
|
|
611
612
|
|
|
612
613
|
function send(obj) {
|
|
613
614
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
615
|
+
if (!obj.id) { obj = Object.assign({ id: 'ui-' + (++_msgId) }, obj); }
|
|
614
616
|
ws.send(JSON.stringify(obj));
|
|
615
617
|
}
|
|
616
618
|
}
|
|
@@ -625,34 +627,35 @@ function buildHtml({
|
|
|
625
627
|
|
|
626
628
|
function handleMessage(msg) {
|
|
627
629
|
switch (msg.type) {
|
|
628
|
-
case 'auth-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
630
|
+
case 'auth-result':
|
|
631
|
+
if (msg.success) {
|
|
632
|
+
onAuthenticated();
|
|
633
|
+
} else {
|
|
634
|
+
setConnStatus('error');
|
|
635
|
+
addSystemMsg('认证失败:' + (msg.message || '无效的 token'));
|
|
636
|
+
}
|
|
635
637
|
break;
|
|
636
638
|
|
|
637
639
|
case 'pong':
|
|
638
640
|
break;
|
|
639
641
|
|
|
640
642
|
case 'session-created':
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
643
|
+
// Server sends { sessionId, sessionType }
|
|
644
|
+
if (msg.sessionId) {
|
|
645
|
+
sessions.set(msg.sessionId, {
|
|
646
|
+
id: msg.sessionId,
|
|
647
|
+
title: (msg.sessionType || 'agent') === 'agent' ? 'Agent 会话' : 'Chat 会话',
|
|
648
|
+
type: msg.sessionType || 'agent',
|
|
649
|
+
createdAt: Date.now(),
|
|
647
650
|
});
|
|
648
651
|
renderSessionList();
|
|
649
|
-
if (currentSessionId === msg.
|
|
652
|
+
if (currentSessionId === msg.sessionId) {
|
|
650
653
|
updateChatHeader();
|
|
651
654
|
}
|
|
652
655
|
}
|
|
653
656
|
break;
|
|
654
657
|
|
|
655
|
-
case 'session-list':
|
|
658
|
+
case 'session-list-result':
|
|
656
659
|
if (Array.isArray(msg.sessions)) {
|
|
657
660
|
msg.sessions.forEach(s => {
|
|
658
661
|
sessions.set(s.id, {
|
|
@@ -666,37 +669,56 @@ function buildHtml({
|
|
|
666
669
|
}
|
|
667
670
|
break;
|
|
668
671
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (msg.sessionId === currentSessionId
|
|
672
|
-
|
|
673
|
-
|
|
672
|
+
// Streaming: chat handler emits response-token per token
|
|
673
|
+
case 'response-token':
|
|
674
|
+
if (msg.sessionId === currentSessionId) {
|
|
675
|
+
if (!streamingMsgId) {
|
|
676
|
+
hideTyping();
|
|
677
|
+
streamBuffer = '';
|
|
678
|
+
streamingMsgId = 'stream-' + Date.now();
|
|
679
|
+
appendAiMsgStreaming(streamingMsgId);
|
|
680
|
+
}
|
|
681
|
+
streamBuffer += (msg.token || '');
|
|
682
|
+
updateStreamingMsg(streamingMsgId, streamBuffer);
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
|
|
686
|
+
// Final response: both agent and chat handlers emit response-complete
|
|
687
|
+
case 'response-complete':
|
|
688
|
+
if (msg.sessionId === currentSessionId) {
|
|
689
|
+
if (streamingMsgId) {
|
|
690
|
+
finalizeStreamingMsg(streamingMsgId, streamBuffer || msg.content || '');
|
|
691
|
+
streamingMsgId = null;
|
|
692
|
+
streamBuffer = '';
|
|
693
|
+
} else {
|
|
694
|
+
// Agent mode: no token stream, show full response at once
|
|
695
|
+
hideTyping();
|
|
696
|
+
if (msg.content) appendAiMsg(msg.content);
|
|
697
|
+
}
|
|
698
|
+
maybeUpdateSessionTitle(currentSessionId);
|
|
674
699
|
}
|
|
675
700
|
break;
|
|
676
701
|
|
|
677
|
-
|
|
702
|
+
// Agent tool events — show as system info
|
|
703
|
+
case 'tool-executing':
|
|
678
704
|
if (msg.sessionId === currentSessionId) {
|
|
679
|
-
|
|
680
|
-
streamBuffer = '';
|
|
681
|
-
streamingMsgId = 'stream-' + Date.now();
|
|
682
|
-
appendAiMsgStreaming(streamingMsgId);
|
|
705
|
+
addSystemMsg('🔧 ' + (msg.display || msg.tool || '工具调用中...'));
|
|
683
706
|
}
|
|
684
707
|
break;
|
|
685
708
|
|
|
686
|
-
case '
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
709
|
+
case 'tool-result':
|
|
710
|
+
// Silently consumed — agent will emit response-complete after
|
|
711
|
+
break;
|
|
712
|
+
|
|
713
|
+
case 'model-switch':
|
|
714
|
+
if (msg.sessionId === currentSessionId) {
|
|
715
|
+
addSystemMsg('🔄 模型切换: ' + msg.from + ' → ' + msg.to + ' (' + msg.reason + ')');
|
|
690
716
|
}
|
|
691
717
|
break;
|
|
692
718
|
|
|
693
|
-
case '
|
|
719
|
+
case 'command-response':
|
|
694
720
|
if (msg.sessionId === currentSessionId) {
|
|
695
|
-
|
|
696
|
-
streamingMsgId = null;
|
|
697
|
-
streamBuffer = '';
|
|
698
|
-
// Update session title from first user message
|
|
699
|
-
maybeUpdateSessionTitle(currentSessionId);
|
|
721
|
+
addSystemMsg('✅ ' + JSON.stringify(msg.result || {}));
|
|
700
722
|
}
|
|
701
723
|
break;
|
|
702
724
|
|
|
@@ -747,15 +769,15 @@ function buildHtml({
|
|
|
747
769
|
ws.onmessage = ev => {
|
|
748
770
|
let msg;
|
|
749
771
|
try { msg = JSON.parse(ev.data); } catch { return; }
|
|
750
|
-
if (msg.type === 'session-created' && msg.
|
|
772
|
+
if (msg.type === 'session-created' && msg.sessionId) {
|
|
751
773
|
// Replace temp id
|
|
752
774
|
sessions.delete(tempId);
|
|
753
|
-
const realId = msg.
|
|
775
|
+
const realId = msg.sessionId;
|
|
754
776
|
sessions.set(realId, {
|
|
755
777
|
id: realId,
|
|
756
|
-
title:
|
|
757
|
-
type: msg.
|
|
758
|
-
createdAt:
|
|
778
|
+
title: selectedSessionType === 'agent' ? 'Agent 会话' : 'Chat 会话',
|
|
779
|
+
type: msg.sessionType || selectedSessionType,
|
|
780
|
+
createdAt: Date.now(),
|
|
759
781
|
});
|
|
760
782
|
if (currentSessionId === tempId) {
|
|
761
783
|
currentSessionId = realId;
|