@flame0510/project-aether 1.3.0 → 1.4.0

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.
Files changed (76) hide show
  1. package/README.md +1 -0
  2. package/agent-templates/README.md +42 -22
  3. package/agent-templates/base-image/Dockerfile +42 -33
  4. package/agent-templates/base-image/entrypoint.sh +67 -12
  5. package/app/agents/BrowserAccessSection.tsx +510 -0
  6. package/app/agents/ChannelManager.tsx +19 -11
  7. package/app/agents/ImageDownloadBanner.tsx +53 -19
  8. package/app/agents/ModelSection.tsx +4 -1
  9. package/app/agents/PageClient.tsx +629 -167
  10. package/app/agents/UpdateSection.tsx +300 -0
  11. package/app/agents/create/PageClient.tsx +11 -49
  12. package/app/api/agents/[id]/backup/route.ts +26 -69
  13. package/app/api/agents/[id]/channels/pairing/route.ts +3 -3
  14. package/app/api/agents/[id]/channels/telegram/route.ts +2 -2
  15. package/app/api/agents/[id]/cold-backup/route.ts +56 -0
  16. package/app/api/agents/[id]/devices/route.ts +126 -0
  17. package/app/api/agents/[id]/invite-link/route.ts +53 -0
  18. package/app/api/agents/[id]/lifecycle/route.ts +3 -0
  19. package/app/api/agents/[id]/open-control-ui/route.ts +58 -0
  20. package/app/api/agents/[id]/recreate/route.ts +33 -163
  21. package/app/api/agents/[id]/restart/route.ts +5 -0
  22. package/app/api/agents/[id]/restore/route.ts +40 -70
  23. package/app/api/agents/[id]/route.ts +38 -150
  24. package/app/api/agents/[id]/update/rollback/route.ts +30 -0
  25. package/app/api/agents/[id]/update/route.ts +50 -0
  26. package/app/api/agents/activity-summary/route.ts +67 -0
  27. package/app/api/agents/create/route.ts +32 -88
  28. package/app/api/agents/devices-summary/route.ts +37 -0
  29. package/app/api/agents/download-image/route.ts +16 -9
  30. package/app/api/agents/image-status/route.ts +31 -111
  31. package/app/api/agents/route.ts +25 -49
  32. package/app/api/agents/token/route.ts +33 -10
  33. package/app/api/assistant/route.ts +2 -2
  34. package/app/api/gateway/agent/route.ts +14 -0
  35. package/app/api/gateway/provider/balance/route.ts +5 -2
  36. package/app/api/gateway/sync.ts +97 -14
  37. package/app/api/setup/agent-image/route.ts +14 -42
  38. package/app/components/DashboardToolbar.tsx +1 -1
  39. package/app/gateway/PageClient.tsx +27 -32
  40. package/bin/rev4a.js +43 -41
  41. package/daemon.js +6 -6
  42. package/docs/ARCHITECTURE.md +95 -9
  43. package/docs/FRONTEND-ARCHITECTURE.md +8 -1
  44. package/docs/REV4A.md +54 -17
  45. package/docs/dev/API-REFERENCE.md +554 -100
  46. package/docs/dev/DATABASE.md +96 -0
  47. package/docs/dev/GATEWAY.md +21 -6
  48. package/docs/rag/DATA-FRESHNESS.md +6 -4
  49. package/docs/rag/GLOSSARY.md +12 -3
  50. package/docs/rag/REV4A-OVERVIEW.md +18 -5
  51. package/docs/rag/WHAT-I-CAN-ANSWER.md +6 -2
  52. package/instrumentation.ts +43 -0
  53. package/lib/agent-busy.ts +21 -0
  54. package/lib/agent-devices.ts +361 -0
  55. package/lib/agent-edit-state.ts +108 -0
  56. package/lib/agent-edit.ts +157 -0
  57. package/lib/agent-images.ts +375 -0
  58. package/lib/agent-ports-server.ts +27 -0
  59. package/lib/agent-ports.ts +68 -0
  60. package/lib/agent-recreate-state.ts +108 -0
  61. package/lib/agent-recreate.ts +305 -0
  62. package/lib/agent-restore-state.ts +107 -0
  63. package/lib/agent-restore.ts +135 -0
  64. package/lib/agent-setup.ts +66 -17
  65. package/lib/agent-update-state.ts +122 -0
  66. package/lib/agent-update.ts +448 -0
  67. package/lib/agent-versions.json +14 -0
  68. package/lib/agent-versions.ts +80 -0
  69. package/lib/buildAgentImage.ts +88 -290
  70. package/lib/channelManager.ts +149 -102
  71. package/lib/cold-backup.ts +354 -0
  72. package/lib/credentials/delivery.ts +3 -3
  73. package/lib/db-bootstrap.mjs +76 -0
  74. package/lib/docker-utils.ts +3 -3
  75. package/lib/provider-balance.ts +33 -12
  76. package/package.json +1 -1
@@ -1,5 +1,13 @@
1
1
  'use client';
2
2
 
3
+ /**
4
+ * Agent base image banner on the Agents page.
5
+ *
6
+ * Shown when no supported OpenClaw version of the agent image is downloaded, when
7
+ * the registry publishes a newer supported version, and while a download runs.
8
+ * Downloading touches no agent: each agent keeps its OpenClaw version until it is
9
+ * updated on its own.
10
+ */
3
11
  import { useState, useEffect, useCallback, useRef } from 'react';
4
12
  import { Button } from '../components/ui';
5
13
 
@@ -7,16 +15,23 @@ interface ImageStatus {
7
15
  exists: boolean;
8
16
  needsUpdate: boolean;
9
17
  downloading: boolean;
18
+ downloadingVersion: string | null;
19
+ localVersions: string[];
20
+ newestLocal: string | null;
21
+ /** Newest supported version on the registry that is not downloaded yet. */
22
+ available: string | null;
10
23
  }
11
24
 
12
25
  const POLL_MS = 2000;
13
26
 
14
27
  export default function ImageDownloadBanner({ onImageReady }: { onImageReady?: () => void }) {
15
- const [status, setStatus] = useState<ImageStatus>({ exists: true, needsUpdate: false, downloading: false });
28
+ const [status, setStatus] = useState<ImageStatus>({
29
+ exists: true, needsUpdate: false, downloading: false, downloadingVersion: null, localVersions: [], newestLocal: null, available: null,
30
+ });
16
31
  const [error, setError] = useState<string | null>(null);
17
- const [justCompleted, setJustCompleted] = useState(false);
32
+ const [justCompleted, setJustCompleted] = useState<string | null>(null);
18
33
  const [actionLoading, setActionLoading] = useState(false);
19
- const wasDownloadingRef = useRef(false);
34
+ const wasDownloadingRef = useRef<string | null>(null);
20
35
  const completedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
21
36
 
22
37
  useEffect(() => {
@@ -32,15 +47,20 @@ export default function ImageDownloadBanner({ onImageReady }: { onImageReady?: (
32
47
 
33
48
  if (data.downloading) {
34
49
  setError(null);
50
+ wasDownloadingRef.current = data.downloadingVersion ?? wasDownloadingRef.current ?? 'image';
35
51
  } else if (wasDownloadingRef.current) {
36
- setJustCompleted(true);
52
+ const finished = wasDownloadingRef.current;
53
+ wasDownloadingRef.current = null;
37
54
  setActionLoading(false);
38
- onImageReady?.();
39
- if (completedTimerRef.current) clearTimeout(completedTimerRef.current);
40
- completedTimerRef.current = setTimeout(() => setJustCompleted(false), 3000);
55
+ if (data.localVersions?.includes(finished)) {
56
+ setJustCompleted(finished);
57
+ onImageReady?.();
58
+ if (completedTimerRef.current) clearTimeout(completedTimerRef.current);
59
+ completedTimerRef.current = setTimeout(() => setJustCompleted(null), 3000);
60
+ } else {
61
+ setError(`The OpenClaw ${finished} download did not complete. Check the Docker connection and try again.`);
62
+ }
41
63
  }
42
-
43
- wasDownloadingRef.current = data.downloading;
44
64
  } catch { /* ignore */ }
45
65
 
46
66
  if (active) setTimeout(tick, POLL_MS);
@@ -56,16 +76,23 @@ export default function ImageDownloadBanner({ onImageReady }: { onImageReady?: (
56
76
  };
57
77
  }, []);
58
78
 
59
- const handleDownload = useCallback(async () => {
79
+ const handleDownload = useCallback(async (version: string | null) => {
60
80
  setActionLoading(true);
61
81
  setError(null);
62
82
  try {
63
- const res = await fetch('/api/agents/download-image', { method: 'POST' });
83
+ const res = await fetch('/api/agents/download-image', {
84
+ method: 'POST',
85
+ headers: { 'Content-Type': 'application/json' },
86
+ body: JSON.stringify(version ? { version } : {}),
87
+ });
64
88
  if (!res.ok) {
65
89
  const data = await res.json().catch(() => ({ error: 'Failed to start download' }));
66
90
  setError(data.error || 'Failed to start download');
67
91
  setActionLoading(false);
92
+ return;
68
93
  }
94
+ const data = await res.json().catch(() => ({})) as { version?: string };
95
+ wasDownloadingRef.current = data.version ?? version ?? 'image';
69
96
  } catch (e: unknown) {
70
97
  setError((e as Error).message || 'Download failed');
71
98
  setActionLoading(false);
@@ -79,6 +106,7 @@ export default function ImageDownloadBanner({ onImageReady }: { onImageReady?: (
79
106
 
80
107
  // ─── Downloading ───
81
108
  if (isDownloading) {
109
+ const version = status.downloadingVersion ?? status.available;
82
110
  return (
83
111
  <Banner downloading>
84
112
  <div style={{ display: 'flex', alignItems: 'center', gap: 8, flex: 1 }}>
@@ -89,7 +117,9 @@ export default function ImageDownloadBanner({ onImageReady }: { onImageReady?: (
89
117
  animation: 'pulse 1.5s ease-in-out infinite',
90
118
  flexShrink: 0,
91
119
  }} />
92
- <span style={{ color: 'var(--violet)', fontWeight: 500 }}>Downloading agent base image…</span>
120
+ <span style={{ color: 'var(--violet)', fontWeight: 500 }}>
121
+ {version ? `Downloading the OpenClaw ${version} agent image…` : 'Downloading the agent image…'}
122
+ </span>
93
123
  </div>
94
124
  <div style={{
95
125
  flex: 1, maxWidth: 120, height: 3,
@@ -107,11 +137,11 @@ export default function ImageDownloadBanner({ onImageReady }: { onImageReady?: (
107
137
  }
108
138
 
109
139
  // ─── Just completed ───
110
- if (justCompleted && !isDownloading) {
140
+ if (justCompleted) {
111
141
  return (
112
142
  <Banner success>
113
143
  <div style={{ flex: 1 }}>
114
- <span style={{ color: '#22c55e', fontWeight: 500 }}>✓ Agent base image ready</span>
144
+ <span style={{ color: '#22c55e', fontWeight: 500 }}>✓ OpenClaw {justCompleted} agent image ready</span>
115
145
  </div>
116
146
  </Banner>
117
147
  );
@@ -122,16 +152,20 @@ export default function ImageDownloadBanner({ onImageReady }: { onImageReady?: (
122
152
  <Banner warning>
123
153
  <div style={{ flex: 1 }}>
124
154
  <strong style={{ color: '#fbbf24' }}>
125
- {status.exists ? '⚠ Agent base image is outdated.' : '⚠ Agent base image not found.'}
155
+ {status.exists
156
+ ? `⚠ OpenClaw ${status.available} is available.`
157
+ : '⚠ Agent base image not found.'}
126
158
  </strong>
127
159
  {' '}{status.exists
128
- ? 'A newer version is available on the registry.'
160
+ ? 'Download it for new agents; existing agents keep their version until they are updated.'
129
161
  : 'Download it to create agent containers.'}
130
162
  {error && <div style={{ color: '#ef4444', fontSize: 11, marginTop: 4 }}>{error}</div>}
131
163
  </div>
132
- <Button variant="primary" size="sm" onClick={handleDownload} loading={actionLoading}>
133
- {actionLoading ? 'Starting…' : status.exists ? 'Download Update' : 'Download Image'}
134
- </Button>
164
+ {(status.exists ? status.available : true) && (
165
+ <Button variant="primary" size="sm" onClick={() => { void handleDownload(status.available); }} loading={actionLoading}>
166
+ {actionLoading ? 'Starting…' : status.exists ? `Download ${status.available}` : 'Download Image'}
167
+ </Button>
168
+ )}
135
169
  </Banner>
136
170
  );
137
171
  }
@@ -68,10 +68,13 @@ function optionLabel(m: ModelOption): string {
68
68
  export default function ModelSection({
69
69
  agentId,
70
70
  onSaved,
71
+ agentBusy,
71
72
  }: {
72
73
  agentId: string;
73
74
  /** Called after a successful save so the agent list can refresh its chip. */
74
75
  onSaved?: () => void;
76
+ /** A long operation is running on this agent: saving would race it. */
77
+ agentBusy?: boolean;
75
78
  }) {
76
79
  const [groups, setGroups] = useState<ProviderGroup[]>([]);
77
80
  const [current, setCurrent] = useState<AgentModel | null>(null);
@@ -293,7 +296,7 @@ export default function ModelSection({
293
296
  </div>
294
297
 
295
298
  <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
296
- <Button variant="primary" size="sm" loading={saving} disabled={!dirty || !primary || primaryUnavailable} onClick={save}>
299
+ <Button variant="primary" size="sm" loading={saving} disabled={!dirty || !primary || primaryUnavailable || !!agentBusy} onClick={save}>
297
300
  {saving ? 'Saving…' : 'Save model'}
298
301
  </Button>
299
302
  {dirty && !saving && (