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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.45.0",
3
+ "version": "0.45.2",
4
4
  "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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 === 0) {
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: false,
22
+ updateAvailable,
16
23
  currentVersion,
17
- latestVersion: currentVersion,
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
- const latest = filtered[0];
22
- const latestVersion = latest.tag_name.replace(/^v/, "");
23
- const updateAvailable = semver.gt(latestVersion, currentVersion);
24
-
25
- return {
26
- updateAvailable,
27
- currentVersion,
28
- latestVersion,
29
- releaseUrl: latest.html_url,
30
- publishedAt: latest.published_at,
31
- releaseNotes: latest.body,
32
- assets: latest.assets.map((a) => ({
33
- name: a.name,
34
- size: a.size,
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(`Update check failed: ${err.message}`);
40
- return {
41
- updateAvailable: false,
42
- currentVersion,
43
- latestVersion: currentVersion,
44
- error: err.message,
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() {
@@ -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.1</span>
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-ok':
629
- onAuthenticated();
630
- break;
631
-
632
- case 'auth-error':
633
- setConnStatus('error');
634
- addSystemMsg('认证失败:' + (msg.error || '无效的 token'));
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
- if (msg.session) {
642
- sessions.set(msg.session.id, {
643
- id: msg.session.id,
644
- title: msg.session.title || '新会话',
645
- type: msg.session.type || 'agent',
646
- createdAt: msg.session.createdAt || Date.now(),
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.session.id) {
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
- case 'session-message':
670
- // Non-streaming response
671
- if (msg.sessionId === currentSessionId && msg.content) {
672
- hideTyping();
673
- appendAiMsg(msg.content);
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
- case 'stream-start':
702
+ // Agent tool events — show as system info
703
+ case 'tool-executing':
678
704
  if (msg.sessionId === currentSessionId) {
679
- hideTyping();
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 'stream-data':
687
- if (msg.sessionId === currentSessionId && streamingMsgId) {
688
- streamBuffer += (msg.data || '');
689
- updateStreamingMsg(streamingMsgId, streamBuffer);
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 'stream-end':
719
+ case 'command-response':
694
720
  if (msg.sessionId === currentSessionId) {
695
- finalizeStreamingMsg(streamingMsgId, streamBuffer);
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.session) {
772
+ if (msg.type === 'session-created' && msg.sessionId) {
751
773
  // Replace temp id
752
774
  sessions.delete(tempId);
753
- const realId = msg.session.id;
775
+ const realId = msg.sessionId;
754
776
  sessions.set(realId, {
755
777
  id: realId,
756
- title: msg.session.title || (selectedSessionType === 'agent' ? 'Agent 会话' : 'Chat 会话'),
757
- type: msg.session.type || selectedSessionType,
758
- createdAt: msg.session.createdAt || Date.now(),
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;