@clawpump/claw-agent 0.1.8 → 0.1.11
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/agent/.mailmap +4 -0
- package/agent/apps/desktop/README.md +3 -3
- package/agent/apps/desktop/assets/icon.icns +0 -0
- package/agent/apps/desktop/assets/icon.ico +0 -0
- package/agent/apps/desktop/assets/icon.png +0 -0
- package/agent/apps/desktop/electron/backend-ready.cjs +2 -2
- package/agent/apps/desktop/electron/dashboard-token.cjs +3 -3
- package/agent/apps/desktop/electron/hardening.cjs +1 -1
- package/agent/apps/desktop/electron/main.cjs +67 -67
- package/agent/apps/desktop/index.html +1 -1
- package/agent/apps/desktop/package.json +16 -16
- package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
- package/agent/apps/desktop/public/claw-mark.png +0 -0
- package/agent/apps/desktop/scripts/after-pack.cjs +1 -1
- package/agent/apps/desktop/scripts/set-exe-identity.cjs +2 -2
- package/agent/apps/desktop/scripts/test-desktop.mjs +3 -3
- package/agent/apps/desktop/src/app/chat/composer/controls.tsx +2 -0
- package/agent/apps/desktop/src/app/chat/composer/index.tsx +10 -0
- package/agent/apps/desktop/src/app/chat/composer/pod-credits.tsx +49 -0
- package/agent/apps/desktop/src/app/chat/index.tsx +1 -1
- package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +4 -2
- package/agent/apps/desktop/src/app/desktop-controller.tsx +18 -0
- package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +1 -1
- package/agent/apps/desktop/src/app/messaging/index.tsx +5 -5
- package/agent/apps/desktop/src/app/routes.ts +9 -1
- package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +3 -3
- package/agent/apps/desktop/src/app/settings/constants.ts +5 -5
- package/agent/apps/desktop/src/app/settings/model-settings.tsx +1 -1
- package/agent/apps/desktop/src/app/settings/providers-settings.tsx +46 -1
- package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +5 -5
- package/agent/apps/desktop/src/app/types.ts +9 -1
- package/agent/apps/desktop/src/app/wallet/index.tsx +244 -0
- package/agent/apps/desktop/src/app/x402/index.tsx +162 -0
- package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +1 -1
- package/agent/apps/desktop/src/components/brand-mark.tsx +2 -2
- package/agent/apps/desktop/src/components/chat/intro-copy.jsonl +6 -6
- package/agent/apps/desktop/src/components/chat/intro.tsx +4 -4
- package/agent/apps/desktop/src/components/model-picker.tsx +64 -4
- package/agent/apps/desktop/src/components/pod-setup-dialog.tsx +227 -0
- package/agent/apps/desktop/src/hermes.ts +109 -3
- package/agent/apps/desktop/src/i18n/en.ts +80 -78
- package/agent/apps/desktop/src/i18n/ja.ts +82 -82
- package/agent/apps/desktop/src/i18n/runtime.test.ts +2 -2
- package/agent/apps/desktop/src/i18n/zh-hant.ts +82 -82
- package/agent/apps/desktop/src/i18n/zh.ts +87 -87
- package/agent/apps/desktop/src/lib/desktop-fs.ts +1 -1
- package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +4 -4
- package/agent/apps/desktop/src/store/composer.ts +7 -0
- package/agent/apps/desktop/src/store/onboarding.ts +5 -5
- package/agent/apps/desktop/src/themes/presets.ts +54 -54
- package/agent/cli.py +184 -10
- package/agent/hermes_cli/distribution.py +188 -8
- package/agent/hermes_cli/gui_uninstall.py +11 -6
- package/agent/hermes_cli/main.py +9 -4
- package/agent/hermes_cli/providers.py +29 -0
- package/agent/hermes_cli/web_server.py +180 -2
- package/agent/plugins/model-providers/usepod/__init__.py +7 -1
- package/agent/scripts/install.sh +3 -1
- package/agent/scripts/release.py +1 -0
- package/agent/web/src/components/ChatSidebar.tsx +5 -0
- package/agent/web/src/components/ModelPickerDialog.tsx +28 -1
- package/agent/web/src/components/PodCredits.tsx +57 -0
- package/agent/web/src/components/PodSetupDialog.tsx +240 -0
- package/agent/web/src/lib/api.ts +23 -0
- package/package.json +1 -1
package/agent/.mailmap
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
Teknium <127238744+teknium1@users.noreply.github.com> <teknium1@gmail.com>
|
|
15
15
|
Teknium <127238744+teknium1@users.noreply.github.com> <teknium@nousresearch.com>
|
|
16
16
|
|
|
17
|
+
# === Tomas Oliver — ClawPump maintainer (multiple emails) ===
|
|
18
|
+
Tomas Oliver <tomi204@users.noreply.github.com> <0xtomi204@gmail.com>
|
|
19
|
+
Tomas Oliver <tomi204@users.noreply.github.com> <tomi204@outlook.com>
|
|
20
|
+
|
|
17
21
|
# === Contributors — personal/work emails mapped to GitHub noreply ===
|
|
18
22
|
# Format: Canonical Name <GH-noreply> <commit-email>
|
|
19
23
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Claw Agent — Desktop 🦀
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://github.com/NousResearch/hermes-agent/releases"><img src="https://img.shields.io/badge/Download-macOS%20%C2%B7%20Windows%20%C2%B7%20Linux-FFD700?style=for-the-badge" alt="Download"></a>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<a href="https://github.com/NousResearch/hermes-agent/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge" alt="License: MIT"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
**
|
|
10
|
+
**Claw Agent by ClawPump — the native desktop app for Solana agents, built on [Hermes Agent](../../README.md) by [Nous Research](https://nousresearch.com).** Same agent, same skills, same memory as the CLI and gateway, in a polished native window — chat with streaming tool output, side-by-side previews, a file browser, voice, and settings, no terminal required. Available for **macOS, Windows, and Linux**.
|
|
11
11
|
|
|
12
12
|
<table>
|
|
13
13
|
<tr><td><b>Chat with the full agent</b></td><td>Streaming responses, live tool activity, structured tool summaries, and the same conversation history as every other Hermes surface.</td></tr>
|
|
@@ -138,4 +138,4 @@ Remove-Item -Recurse -Force "$env:LOCALAPPDATA\hermes\hermes-agent\venv"
|
|
|
138
138
|
|
|
139
139
|
MIT — see [LICENSE](../../LICENSE).
|
|
140
140
|
|
|
141
|
-
Built by [Nous Research](https://nousresearch.com).
|
|
141
|
+
Built on Hermes by [Nous Research](https://nousresearch.com). ClawPump distribution.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -44,7 +44,7 @@ function waitForDashboardPort(child, timeoutMs = 45_000) {
|
|
|
44
44
|
|
|
45
45
|
function onExit(code, signal) {
|
|
46
46
|
cleanup()
|
|
47
|
-
reject(new Error(`
|
|
47
|
+
reject(new Error(`Claw Agent backend: exited before port announcement (${signal || code})`))
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function onError(err) {
|
|
@@ -54,7 +54,7 @@ function waitForDashboardPort(child, timeoutMs = 45_000) {
|
|
|
54
54
|
|
|
55
55
|
const timer = setTimeout(() => {
|
|
56
56
|
cleanup()
|
|
57
|
-
reject(new Error(`Timed out waiting for
|
|
57
|
+
reject(new Error(`Timed out waiting for Claw Agent backend port announcement (${timeoutMs}ms)`))
|
|
58
58
|
}, timeoutMs)
|
|
59
59
|
|
|
60
60
|
child.stdout.on('data', onData)
|
|
@@ -12,13 +12,13 @@ const DEFAULT_TOKEN_FETCH_TIMEOUT_MS = 3_000
|
|
|
12
12
|
async function fetchPublicText(url, options = {}) {
|
|
13
13
|
const { protocol } = new URL(url)
|
|
14
14
|
if (protocol !== 'http:' && protocol !== 'https:') {
|
|
15
|
-
throw new Error(`Unsupported
|
|
15
|
+
throw new Error(`Unsupported Claw Agent backend URL protocol: ${protocol}`)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TOKEN_FETCH_TIMEOUT_MS
|
|
19
19
|
const res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) }).catch(error => {
|
|
20
20
|
if (error.name === 'TimeoutError') {
|
|
21
|
-
throw new Error(`Timed out connecting to
|
|
21
|
+
throw new Error(`Timed out connecting to Claw Agent backend after ${timeoutMs}ms`)
|
|
22
22
|
}
|
|
23
23
|
throw error
|
|
24
24
|
})
|
|
@@ -73,7 +73,7 @@ function isForeignBackendToken({ servedToken, spawnToken, childAlive }) {
|
|
|
73
73
|
* failing loudly on a foreign backend. `childAlive` is a thunk so liveness is
|
|
74
74
|
* sampled after the fetch, not before.
|
|
75
75
|
*/
|
|
76
|
-
async function adoptServedDashboardToken(baseUrl, spawnToken, { childAlive, label = '
|
|
76
|
+
async function adoptServedDashboardToken(baseUrl, spawnToken, { childAlive, label = 'Claw Agent backend', ...options }) {
|
|
77
77
|
const servedToken = await resolveServedDashboardToken(baseUrl, spawnToken, options).catch(error => {
|
|
78
78
|
options.rememberLog?.(`[boot] could not read served dashboard token (${label}): ${error.message}`)
|
|
79
79
|
return spawnToken
|
|
@@ -39,7 +39,7 @@ function encryptDesktopSecret(value, safeStorageApi) {
|
|
|
39
39
|
|
|
40
40
|
if (!encryptionAvailable) {
|
|
41
41
|
throw new Error(
|
|
42
|
-
'Secure token storage is unavailable, so
|
|
42
|
+
'Secure token storage is unavailable, so Claw Agent Desktop cannot save remote gateway tokens. ' +
|
|
43
43
|
'Set HERMES_DESKTOP_REMOTE_URL and HERMES_DESKTOP_REMOTE_TOKEN in your environment, or enable OS keychain access and try again.'
|
|
44
44
|
)
|
|
45
45
|
}
|
|
@@ -268,7 +268,7 @@ function resolveHermesHome() {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
const HERMES_HOME = resolveHermesHome()
|
|
271
|
-
// ACTIVE_HERMES_ROOT — the canonical mutable
|
|
271
|
+
// ACTIVE_HERMES_ROOT — the canonical mutable Claw Agent install. Same path
|
|
272
272
|
// install.ps1 / install.sh use, so a desktop-only user and a CLI-only user end
|
|
273
273
|
// up with identical layouts and can share one install.
|
|
274
274
|
const ACTIVE_HERMES_ROOT = path.join(HERMES_HOME, 'hermes-agent')
|
|
@@ -335,7 +335,7 @@ const BOOT_FAKE_STEP_MS = (() => {
|
|
|
335
335
|
if (!Number.isFinite(raw) || raw <= 0) return 650
|
|
336
336
|
return Math.max(120, raw)
|
|
337
337
|
})()
|
|
338
|
-
const APP_NAME = '
|
|
338
|
+
const APP_NAME = 'Claw Agent'
|
|
339
339
|
const TITLEBAR_HEIGHT = 34
|
|
340
340
|
const MACOS_TRAFFIC_LIGHTS_HEIGHT = 14
|
|
341
341
|
const WINDOW_BUTTON_POSITION = {
|
|
@@ -597,7 +597,7 @@ app.setName(APP_NAME)
|
|
|
597
597
|
app.setAboutPanelOptions({
|
|
598
598
|
applicationName: APP_NAME,
|
|
599
599
|
applicationVersion: resolveHermesVersion(),
|
|
600
|
-
copyright: 'Copyright © 2026 Nous Research'
|
|
600
|
+
copyright: 'Copyright © 2026 Nous Research · ClawPump distribution'
|
|
601
601
|
})
|
|
602
602
|
|
|
603
603
|
// Custom scheme for streaming local media (video/audio) into the renderer.
|
|
@@ -709,7 +709,7 @@ let nativeThemeListenerInstalled = false
|
|
|
709
709
|
let bootProgressState = {
|
|
710
710
|
error: null,
|
|
711
711
|
fakeMode: BOOT_FAKE_MODE,
|
|
712
|
-
message: 'Waiting to start
|
|
712
|
+
message: 'Waiting to start Claw Agent backend',
|
|
713
713
|
phase: 'idle',
|
|
714
714
|
progress: 0,
|
|
715
715
|
running: false,
|
|
@@ -1801,7 +1801,7 @@ async function applyUpdates(opts = {}) {
|
|
|
1801
1801
|
return { ok: true, manual: true, command, hermesRoot: updateRoot }
|
|
1802
1802
|
}
|
|
1803
1803
|
|
|
1804
|
-
emitUpdateProgress({ stage: 'restart', message: 'Handing off to the
|
|
1804
|
+
emitUpdateProgress({ stage: 'restart', message: 'Handing off to the Claw Agent updater…', percent: 100 })
|
|
1805
1805
|
repairMacUpdaterHelper(updater)
|
|
1806
1806
|
|
|
1807
1807
|
const updateRoot = resolveUpdateRoot()
|
|
@@ -1998,7 +1998,7 @@ async function applyUpdatesPosixInApp() {
|
|
|
1998
1998
|
// best effort
|
|
1999
1999
|
}
|
|
2000
2000
|
|
|
2001
|
-
emitUpdateProgress({ stage: 'update', message: 'Updating
|
|
2001
|
+
emitUpdateProgress({ stage: 'update', message: 'Updating Claw Agent (git + dependencies)…', percent: 10 })
|
|
2002
2002
|
const updated = await runStreamedUpdate(hermes, ['update', '--yes', ...branchArgs], {
|
|
2003
2003
|
cwd: updateRoot,
|
|
2004
2004
|
env,
|
|
@@ -2022,15 +2022,15 @@ async function applyUpdatesPosixInApp() {
|
|
|
2022
2022
|
if (rebuilt.code !== 0) {
|
|
2023
2023
|
emitUpdateProgress({
|
|
2024
2024
|
stage: 'error',
|
|
2025
|
-
message:
|
|
2025
|
+
message: `Backend updated, but the desktop rebuild failed. Restart ${APP_NAME} to retry.`,
|
|
2026
2026
|
error: rebuilt.error || 'rebuild-failed'
|
|
2027
2027
|
})
|
|
2028
2028
|
return { ok: false, backendUpdated: true, error: 'desktop rebuild failed' }
|
|
2029
2029
|
}
|
|
2030
2030
|
|
|
2031
2031
|
const rebuiltApp = [
|
|
2032
|
-
path.join(updateRoot, 'apps', 'desktop', 'release', 'mac-arm64', '
|
|
2033
|
-
path.join(updateRoot, 'apps', 'desktop', 'release', 'mac', '
|
|
2032
|
+
path.join(updateRoot, 'apps', 'desktop', 'release', 'mac-arm64', 'Claw Agent.app'),
|
|
2033
|
+
path.join(updateRoot, 'apps', 'desktop', 'release', 'mac', 'Claw Agent.app')
|
|
2034
2034
|
].find(directoryExists)
|
|
2035
2035
|
const targetApp = runningAppBundle()
|
|
2036
2036
|
|
|
@@ -2039,7 +2039,7 @@ async function applyUpdatesPosixInApp() {
|
|
|
2039
2039
|
if (!rebuiltApp || !targetApp) {
|
|
2040
2040
|
emitUpdateProgress({
|
|
2041
2041
|
stage: 'done',
|
|
2042
|
-
message:
|
|
2042
|
+
message: `Backend updated. Restart ${APP_NAME} to load the new version.`,
|
|
2043
2043
|
percent: 100
|
|
2044
2044
|
})
|
|
2045
2045
|
return { ok: true, backendUpdated: true, rebuiltApp: rebuiltApp || null }
|
|
@@ -2075,7 +2075,7 @@ fi
|
|
|
2075
2075
|
} catch (err) {
|
|
2076
2076
|
emitUpdateProgress({
|
|
2077
2077
|
stage: 'done',
|
|
2078
|
-
message:
|
|
2078
|
+
message: `Backend + app updated. Restart ${APP_NAME} to load the new version.`,
|
|
2079
2079
|
percent: 100
|
|
2080
2080
|
})
|
|
2081
2081
|
rememberLog(`[updates] could not write swap script: ${err.message}; rebuilt app at ${rebuiltApp}`)
|
|
@@ -2339,7 +2339,7 @@ function resolveHermesBackend(dashboardArgs) {
|
|
|
2339
2339
|
// checkout. Honour it as-is (no bootstrap; the user is driving).
|
|
2340
2340
|
const overrideRoot = process.env.HERMES_DESKTOP_HERMES_ROOT && path.resolve(process.env.HERMES_DESKTOP_HERMES_ROOT)
|
|
2341
2341
|
if (overrideRoot && isHermesSourceRoot(overrideRoot)) {
|
|
2342
|
-
const backend = createPythonBackend(overrideRoot, `
|
|
2342
|
+
const backend = createPythonBackend(overrideRoot, `Claw Agent source at ${overrideRoot}`, dashboardArgs)
|
|
2343
2343
|
if (backend) return backend
|
|
2344
2344
|
}
|
|
2345
2345
|
|
|
@@ -2348,7 +2348,7 @@ function resolveHermesBackend(dashboardArgs) {
|
|
|
2348
2348
|
// installed `hermes` on PATH so local Python edits are actually exercised.
|
|
2349
2349
|
// (In dev with no checkout, SOURCE_REPO_ROOT won't pass isHermesSourceRoot.)
|
|
2350
2350
|
if (!IS_PACKAGED && isHermesSourceRoot(SOURCE_REPO_ROOT)) {
|
|
2351
|
-
const backend = createPythonBackend(SOURCE_REPO_ROOT, `
|
|
2351
|
+
const backend = createPythonBackend(SOURCE_REPO_ROOT, `Claw Agent source at ${SOURCE_REPO_ROOT}`, dashboardArgs)
|
|
2352
2352
|
if (backend) return backend
|
|
2353
2353
|
}
|
|
2354
2354
|
|
|
@@ -2386,7 +2386,7 @@ function resolveHermesBackend(dashboardArgs) {
|
|
|
2386
2386
|
|
|
2387
2387
|
if (hermesCommand) {
|
|
2388
2388
|
if (looksLikeDesktopAppBinary(hermesCommand)) {
|
|
2389
|
-
rememberLog(`Ignoring desktop app executable on PATH while resolving
|
|
2389
|
+
rememberLog(`Ignoring desktop app executable on PATH while resolving Claw Agent CLI: ${hermesCommand}`)
|
|
2390
2390
|
hermesCommand = null
|
|
2391
2391
|
}
|
|
2392
2392
|
}
|
|
@@ -2402,7 +2402,7 @@ function resolveHermesBackend(dashboardArgs) {
|
|
|
2402
2402
|
const shellForProbe = isCommandScript(hermesCommand)
|
|
2403
2403
|
if (verifyHermesCli(hermesCommand, { shell: shellForProbe })) {
|
|
2404
2404
|
return {
|
|
2405
|
-
label: `existing
|
|
2405
|
+
label: `existing Claw Agent CLI at ${hermesCommand}`,
|
|
2406
2406
|
command: hermesCommand,
|
|
2407
2407
|
args: dashboardArgs,
|
|
2408
2408
|
bootstrap: false,
|
|
@@ -2412,7 +2412,7 @@ function resolveHermesBackend(dashboardArgs) {
|
|
|
2412
2412
|
}
|
|
2413
2413
|
}
|
|
2414
2414
|
rememberLog(
|
|
2415
|
-
`Ignoring existing
|
|
2415
|
+
`Ignoring existing Claw Agent CLI at ${hermesCommand}: --version probe failed; falling through to bootstrap.`
|
|
2416
2416
|
)
|
|
2417
2417
|
}
|
|
2418
2418
|
}
|
|
@@ -2456,7 +2456,7 @@ function resolveHermesBackend(dashboardArgs) {
|
|
|
2456
2456
|
// is a recoverable state the GUI can drive through.
|
|
2457
2457
|
return {
|
|
2458
2458
|
kind: 'bootstrap-needed',
|
|
2459
|
-
label: '
|
|
2459
|
+
label: 'Claw Agent not installed yet; bootstrap required',
|
|
2460
2460
|
command: null,
|
|
2461
2461
|
args: dashboardArgs,
|
|
2462
2462
|
bootstrap: true,
|
|
@@ -2486,10 +2486,10 @@ async function ensureRuntime(backend) {
|
|
|
2486
2486
|
// will rewire startup to spawn the window first and route bootstrap events
|
|
2487
2487
|
// to a renderer-side install overlay.
|
|
2488
2488
|
if (backend.kind === 'bootstrap-needed') {
|
|
2489
|
-
rememberLog('[bootstrap] no
|
|
2489
|
+
rememberLog('[bootstrap] no Claw Agent install found; starting first-launch bootstrap')
|
|
2490
2490
|
|
|
2491
2491
|
if (await handOffWindowsBootstrapRecovery('bootstrap-needed')) {
|
|
2492
|
-
const handoffError = new Error('
|
|
2492
|
+
const handoffError = new Error('Claw Agent recovery was handed off to Claw Agent Setup. The desktop will restart when recovery completes.')
|
|
2493
2493
|
handoffError.isBootstrapFailure = true
|
|
2494
2494
|
handoffError.bootstrapHandedOff = true
|
|
2495
2495
|
bootstrapFailure = handoffError
|
|
@@ -2543,7 +2543,7 @@ async function ensureRuntime(backend) {
|
|
|
2543
2543
|
bootstrapAbortController = null
|
|
2544
2544
|
|
|
2545
2545
|
if (bootstrapResult.cancelled) {
|
|
2546
|
-
const cancelledError = new Error('
|
|
2546
|
+
const cancelledError = new Error('Claw Agent install was cancelled.')
|
|
2547
2547
|
cancelledError.isBootstrapFailure = true
|
|
2548
2548
|
cancelledError.bootstrapCancelled = true
|
|
2549
2549
|
bootstrapFailure = cancelledError
|
|
@@ -2552,7 +2552,7 @@ async function ensureRuntime(backend) {
|
|
|
2552
2552
|
|
|
2553
2553
|
if (!bootstrapResult.ok) {
|
|
2554
2554
|
const bootstrapError = new Error(
|
|
2555
|
-
`
|
|
2555
|
+
`Claw Agent bootstrap failed${bootstrapResult.failedStage ? ` at stage '${bootstrapResult.failedStage}'` : ''}: ` +
|
|
2556
2556
|
`${bootstrapResult.error || 'unknown error'}. ` +
|
|
2557
2557
|
`Check ${path.join(HERMES_HOME, 'logs', 'desktop.log')} for the full transcript.`
|
|
2558
2558
|
)
|
|
@@ -2579,7 +2579,7 @@ async function ensureRuntime(backend) {
|
|
|
2579
2579
|
// attests they ran successfully).
|
|
2580
2580
|
if (!isHermesSourceRoot(ACTIVE_HERMES_ROOT)) {
|
|
2581
2581
|
throw new Error(
|
|
2582
|
-
`
|
|
2582
|
+
`Claw Agent install at ${ACTIVE_HERMES_ROOT} is missing or incomplete. ` +
|
|
2583
2583
|
'Reinstall via the desktop installer or scripts/install.ps1.'
|
|
2584
2584
|
)
|
|
2585
2585
|
}
|
|
@@ -2592,10 +2592,10 @@ async function ensureRuntime(backend) {
|
|
|
2592
2592
|
// here via an external `hermes` on PATH, this check still helps.
|
|
2593
2593
|
if (IS_WINDOWS && !findGitBash()) {
|
|
2594
2594
|
throw new Error(
|
|
2595
|
-
'Git for Windows is required for
|
|
2595
|
+
'Git for Windows is required for Claw Agent on Windows (provides Git Bash, ' +
|
|
2596
2596
|
"which the agent's terminal tool uses). Install it from " +
|
|
2597
2597
|
'https://git-scm.com/download/win or run `winget install -e --id Git.Git`, ' +
|
|
2598
|
-
'then relaunch
|
|
2598
|
+
'then relaunch Claw Agent.'
|
|
2599
2599
|
)
|
|
2600
2600
|
}
|
|
2601
2601
|
|
|
@@ -2609,7 +2609,7 @@ async function ensureRuntime(backend) {
|
|
|
2609
2609
|
// install.ps1 succeeds. If we hit this, the user (or a deleted venv)
|
|
2610
2610
|
// broke the invariant; tell them to re-run the install.
|
|
2611
2611
|
throw new Error(
|
|
2612
|
-
`
|
|
2612
|
+
`Claw Agent venv missing at ${VENV_ROOT}. Re-run the desktop installer or ` + '`scripts/install.ps1` to rebuild it.'
|
|
2613
2613
|
)
|
|
2614
2614
|
}
|
|
2615
2615
|
|
|
@@ -2617,7 +2617,7 @@ async function ensureRuntime(backend) {
|
|
|
2617
2617
|
backend.label = `Hermes at ${ACTIVE_HERMES_ROOT} (venv: ${VENV_ROOT})`
|
|
2618
2618
|
updateBootProgress({
|
|
2619
2619
|
phase: 'runtime.ready',
|
|
2620
|
-
message: '
|
|
2620
|
+
message: 'Claw Agent runtime is ready',
|
|
2621
2621
|
progress: 82,
|
|
2622
2622
|
running: true,
|
|
2623
2623
|
error: null
|
|
@@ -2634,7 +2634,7 @@ function fetchJson(url, token, options = {}) {
|
|
|
2634
2634
|
const timeoutMs = resolveTimeoutMs(options.timeoutMs, DEFAULT_FETCH_TIMEOUT_MS)
|
|
2635
2635
|
|
|
2636
2636
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
2637
|
-
reject(new Error(`Unsupported
|
|
2637
|
+
reject(new Error(`Unsupported Claw Agent backend URL protocol: ${parsed.protocol}`))
|
|
2638
2638
|
return
|
|
2639
2639
|
}
|
|
2640
2640
|
|
|
@@ -2672,7 +2672,7 @@ function fetchJson(url, token, options = {}) {
|
|
|
2672
2672
|
reject(
|
|
2673
2673
|
new Error(
|
|
2674
2674
|
`Expected JSON from ${url} but got HTML (status ${res.statusCode}). ` +
|
|
2675
|
-
'The endpoint is likely missing on the
|
|
2675
|
+
'The endpoint is likely missing on the Claw Agent backend.'
|
|
2676
2676
|
)
|
|
2677
2677
|
)
|
|
2678
2678
|
return
|
|
@@ -2688,7 +2688,7 @@ function fetchJson(url, token, options = {}) {
|
|
|
2688
2688
|
|
|
2689
2689
|
req.on('error', reject)
|
|
2690
2690
|
req.setTimeout(timeoutMs, () => {
|
|
2691
|
-
req.destroy(new Error(`Timed out connecting to
|
|
2691
|
+
req.destroy(new Error(`Timed out connecting to Claw Agent backend after ${timeoutMs}ms`))
|
|
2692
2692
|
})
|
|
2693
2693
|
if (body) req.write(body)
|
|
2694
2694
|
req.end()
|
|
@@ -2714,7 +2714,7 @@ function fetchPublicJson(url, options = {}) {
|
|
|
2714
2714
|
const timeoutMs = resolveTimeoutMs(options.timeoutMs, DEFAULT_FETCH_TIMEOUT_MS)
|
|
2715
2715
|
|
|
2716
2716
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
2717
|
-
reject(new Error(`Unsupported
|
|
2717
|
+
reject(new Error(`Unsupported Claw Agent backend URL protocol: ${parsed.protocol}`))
|
|
2718
2718
|
return
|
|
2719
2719
|
}
|
|
2720
2720
|
|
|
@@ -2746,7 +2746,7 @@ function fetchPublicJson(url, options = {}) {
|
|
|
2746
2746
|
reject(
|
|
2747
2747
|
new Error(
|
|
2748
2748
|
`Expected JSON from ${url} but got HTML (status ${res.statusCode}). ` +
|
|
2749
|
-
'The endpoint is likely missing on the
|
|
2749
|
+
'The endpoint is likely missing on the Claw Agent backend.'
|
|
2750
2750
|
)
|
|
2751
2751
|
)
|
|
2752
2752
|
return
|
|
@@ -2762,7 +2762,7 @@ function fetchPublicJson(url, options = {}) {
|
|
|
2762
2762
|
|
|
2763
2763
|
req.on('error', reject)
|
|
2764
2764
|
req.setTimeout(timeoutMs, () => {
|
|
2765
|
-
req.destroy(new Error(`Timed out connecting to
|
|
2765
|
+
req.destroy(new Error(`Timed out connecting to Claw Agent backend after ${timeoutMs}ms`))
|
|
2766
2766
|
})
|
|
2767
2767
|
if (body) req.write(body)
|
|
2768
2768
|
req.end()
|
|
@@ -3288,7 +3288,7 @@ async function waitForHermes(baseUrl, token) {
|
|
|
3288
3288
|
}
|
|
3289
3289
|
}
|
|
3290
3290
|
|
|
3291
|
-
throw new Error(`
|
|
3291
|
+
throw new Error(`Claw Agent backend did not become ready: ${lastError?.message || 'timeout'}`)
|
|
3292
3292
|
}
|
|
3293
3293
|
|
|
3294
3294
|
function getWindowButtonPosition() {
|
|
@@ -3734,7 +3734,7 @@ function installMediaPermissions() {
|
|
|
3734
3734
|
// ---------------------------------------------------------------------------
|
|
3735
3735
|
// OAuth remote-gateway auth.
|
|
3736
3736
|
//
|
|
3737
|
-
// Hosted
|
|
3737
|
+
// Hosted Claw Agent gateways gate the dashboard behind an OAuth provider (e.g.
|
|
3738
3738
|
// Nous Research) instead of a static session token. The auth model is
|
|
3739
3739
|
// fundamentally different from the token path:
|
|
3740
3740
|
//
|
|
@@ -3875,7 +3875,7 @@ function openOauthLoginWindow(baseUrl) {
|
|
|
3875
3875
|
win = new BrowserWindow({
|
|
3876
3876
|
width: 520,
|
|
3877
3877
|
height: 720,
|
|
3878
|
-
title: 'Sign in to
|
|
3878
|
+
title: 'Sign in to Claw Agent gateway',
|
|
3879
3879
|
autoHideMenuBar: true,
|
|
3880
3880
|
webPreferences: {
|
|
3881
3881
|
contextIsolation: true,
|
|
@@ -3930,7 +3930,7 @@ function fetchJsonViaOauthSession(url, options = {}) {
|
|
|
3930
3930
|
return
|
|
3931
3931
|
}
|
|
3932
3932
|
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
3933
|
-
reject(new Error(`Unsupported
|
|
3933
|
+
reject(new Error(`Unsupported Claw Agent backend URL protocol: ${parsed.protocol}`))
|
|
3934
3934
|
return
|
|
3935
3935
|
}
|
|
3936
3936
|
const body = serializeJsonBody(options.body)
|
|
@@ -3953,7 +3953,7 @@ function fetchJsonViaOauthSession(url, options = {}) {
|
|
|
3953
3953
|
} catch {
|
|
3954
3954
|
// already finished
|
|
3955
3955
|
}
|
|
3956
|
-
reject(new Error(`Timed out connecting to
|
|
3956
|
+
reject(new Error(`Timed out connecting to Claw Agent backend after ${timeoutMs}ms`))
|
|
3957
3957
|
}, timeoutMs)
|
|
3958
3958
|
|
|
3959
3959
|
request.on('response', res => {
|
|
@@ -4288,7 +4288,7 @@ async function buildRemoteConnection(rawUrl, authMode, token, source) {
|
|
|
4288
4288
|
// the authoritative liveness check.
|
|
4289
4289
|
if (!(await hasLiveOauthSession(baseUrl))) {
|
|
4290
4290
|
const err = new Error(
|
|
4291
|
-
'Remote
|
|
4291
|
+
'Remote Claw Agent gateway uses OAuth, but you are not signed in. ' +
|
|
4292
4292
|
'Open Settings → Gateway and click "Sign in", or switch back to Local.'
|
|
4293
4293
|
)
|
|
4294
4294
|
err.needsOauthLogin = true
|
|
@@ -4320,7 +4320,7 @@ async function buildRemoteConnection(rawUrl, authMode, token, source) {
|
|
|
4320
4320
|
|
|
4321
4321
|
if (!token) {
|
|
4322
4322
|
throw new Error(
|
|
4323
|
-
'Remote
|
|
4323
|
+
'Remote Claw Agent gateway is selected, but no session token is saved. ' +
|
|
4324
4324
|
'Open Settings → Gateway and save a token, or switch back to Local.'
|
|
4325
4325
|
)
|
|
4326
4326
|
}
|
|
@@ -4361,7 +4361,7 @@ async function resolveRemoteBackend(profile) {
|
|
|
4361
4361
|
if (!rawEnvToken) {
|
|
4362
4362
|
throw new Error(
|
|
4363
4363
|
'HERMES_DESKTOP_REMOTE_URL is set but HERMES_DESKTOP_REMOTE_TOKEN is not. ' +
|
|
4364
|
-
'Both must be provided to connect to a remote
|
|
4364
|
+
'Both must be provided to connect to a remote Claw Agent backend.'
|
|
4365
4365
|
)
|
|
4366
4366
|
}
|
|
4367
4367
|
return buildRemoteConnection(rawEnvUrl, 'token', rawEnvToken, 'env')
|
|
@@ -4511,7 +4511,7 @@ async function testDesktopConnectionConfig(input = {}) {
|
|
|
4511
4511
|
// connects — a separate transport with separate server-side guards (Host/
|
|
4512
4512
|
// Origin, ws-ticket/token auth). Validating only the HTTP side produced a
|
|
4513
4513
|
// false-positive "reachable" while the real boot still failed with "Could not
|
|
4514
|
-
// connect to
|
|
4514
|
+
// connect to Claw Agent gateway". Mirror the renderer's connect here so the test
|
|
4515
4515
|
// reflects the full path the app actually uses.
|
|
4516
4516
|
const wsUrl = await resolveTestWsUrl(baseUrl, authMode, token, { mintTicket: mintGatewayWsTicket })
|
|
4517
4517
|
// Skip the WS leg only when the runtime genuinely lacks a WebSocket (so an
|
|
@@ -4711,7 +4711,7 @@ async function spawnPoolBackend(profile, entry) {
|
|
|
4711
4711
|
const hermesCwd = resolveHermesCwd()
|
|
4712
4712
|
const webDist = resolveWebDist()
|
|
4713
4713
|
|
|
4714
|
-
rememberLog(`Starting
|
|
4714
|
+
rememberLog(`Starting Claw Agent backend for profile "${profile}" via ${backend.label}`)
|
|
4715
4715
|
|
|
4716
4716
|
const child = spawn(
|
|
4717
4717
|
backend.command,
|
|
@@ -4748,16 +4748,16 @@ async function spawnPoolBackend(profile, entry) {
|
|
|
4748
4748
|
rejectStart = reject
|
|
4749
4749
|
})
|
|
4750
4750
|
child.once('error', error => {
|
|
4751
|
-
rememberLog(`
|
|
4751
|
+
rememberLog(`Claw Agent backend for profile "${profile}" failed to start: ${error.message}`)
|
|
4752
4752
|
backendPool.delete(profile)
|
|
4753
4753
|
rejectStart?.(error)
|
|
4754
4754
|
})
|
|
4755
4755
|
child.once('exit', (code, signal) => {
|
|
4756
|
-
rememberLog(`
|
|
4756
|
+
rememberLog(`Claw Agent backend for profile "${profile}" exited (${signal || code})`)
|
|
4757
4757
|
backendPool.delete(profile)
|
|
4758
4758
|
if (!ready) {
|
|
4759
4759
|
rejectStart?.(
|
|
4760
|
-
new Error(`
|
|
4760
|
+
new Error(`Claw Agent backend for profile "${profile}" exited before it became ready (${signal || code}).`)
|
|
4761
4761
|
)
|
|
4762
4762
|
}
|
|
4763
4763
|
})
|
|
@@ -4771,7 +4771,7 @@ async function spawnPoolBackend(profile, entry) {
|
|
|
4771
4771
|
ready = true
|
|
4772
4772
|
const authToken = await adoptServedDashboardToken(baseUrl, token, {
|
|
4773
4773
|
childAlive: () => child.exitCode === null && !child.killed,
|
|
4774
|
-
label: `
|
|
4774
|
+
label: `Claw Agent backend for profile "${profile}"`,
|
|
4775
4775
|
rememberLog
|
|
4776
4776
|
})
|
|
4777
4777
|
entry.token = authToken
|
|
@@ -4879,16 +4879,16 @@ async function startHermes() {
|
|
|
4879
4879
|
if (connectionPromise) return connectionPromise
|
|
4880
4880
|
|
|
4881
4881
|
connectionPromise = (async () => {
|
|
4882
|
-
await advanceBootProgress('backend.resolve', 'Resolving
|
|
4882
|
+
await advanceBootProgress('backend.resolve', 'Resolving Claw Agent backend', 8)
|
|
4883
4883
|
// Resolve for the desktop's primary profile so a per-profile remote
|
|
4884
4884
|
// override on the active profile is honored (falls back to env / global).
|
|
4885
4885
|
const remote = await resolveRemoteBackend(primaryProfileKey())
|
|
4886
4886
|
if (remote) {
|
|
4887
|
-
await advanceBootProgress('backend.remote', `Connecting to remote
|
|
4887
|
+
await advanceBootProgress('backend.remote', `Connecting to remote Claw Agent backend at ${remote.baseUrl}`, 24)
|
|
4888
4888
|
await waitForHermes(remote.baseUrl, remote.token)
|
|
4889
4889
|
updateBootProgress({
|
|
4890
4890
|
phase: 'backend.ready',
|
|
4891
|
-
message: 'Remote
|
|
4891
|
+
message: 'Remote Claw Agent backend is ready',
|
|
4892
4892
|
progress: 94,
|
|
4893
4893
|
running: true,
|
|
4894
4894
|
error: null
|
|
@@ -4917,13 +4917,13 @@ async function startHermes() {
|
|
|
4917
4917
|
if (activeProfile) {
|
|
4918
4918
|
dashboardArgs.unshift('--profile', activeProfile)
|
|
4919
4919
|
}
|
|
4920
|
-
await advanceBootProgress('backend.runtime', 'Resolving
|
|
4920
|
+
await advanceBootProgress('backend.runtime', 'Resolving Claw Agent runtime', 28)
|
|
4921
4921
|
const backend = await ensureRuntime(resolveHermesBackend(dashboardArgs))
|
|
4922
4922
|
const hermesCwd = resolveHermesCwd()
|
|
4923
4923
|
const webDist = resolveWebDist()
|
|
4924
4924
|
|
|
4925
|
-
await advanceBootProgress('backend.spawn', `Starting
|
|
4926
|
-
rememberLog(`Starting
|
|
4925
|
+
await advanceBootProgress('backend.spawn', `Starting Claw Agent backend via ${backend.label}`, 84)
|
|
4926
|
+
rememberLog(`Starting Claw Agent backend via ${backend.label}`)
|
|
4927
4927
|
|
|
4928
4928
|
hermesProcess = spawn(
|
|
4929
4929
|
backend.command,
|
|
@@ -4962,11 +4962,11 @@ async function startHermes() {
|
|
|
4962
4962
|
rejectBackendStart = reject
|
|
4963
4963
|
})
|
|
4964
4964
|
hermesProcess.once('error', error => {
|
|
4965
|
-
rememberLog(`
|
|
4965
|
+
rememberLog(`Claw Agent backend failed to start: ${error.message}`)
|
|
4966
4966
|
updateBootProgress(
|
|
4967
4967
|
{
|
|
4968
4968
|
error: error.message,
|
|
4969
|
-
message: `
|
|
4969
|
+
message: `Claw Agent backend failed to start: ${error.message}`,
|
|
4970
4970
|
phase: 'backend.error',
|
|
4971
4971
|
running: false
|
|
4972
4972
|
},
|
|
@@ -4978,12 +4978,12 @@ async function startHermes() {
|
|
|
4978
4978
|
rejectBackendStart?.(error)
|
|
4979
4979
|
})
|
|
4980
4980
|
hermesProcess.once('exit', (code, signal) => {
|
|
4981
|
-
rememberLog(`
|
|
4981
|
+
rememberLog(`Claw Agent backend exited (${signal || code})`)
|
|
4982
4982
|
hermesProcess = null
|
|
4983
4983
|
connectionPromise = null
|
|
4984
4984
|
sendBackendExit({ code, signal })
|
|
4985
4985
|
if (!backendReady) {
|
|
4986
|
-
const message = `
|
|
4986
|
+
const message = `Claw Agent backend exited before it became ready (${signal || code}).`
|
|
4987
4987
|
updateBootProgress(
|
|
4988
4988
|
{
|
|
4989
4989
|
error: message,
|
|
@@ -4995,18 +4995,18 @@ async function startHermes() {
|
|
|
4995
4995
|
)
|
|
4996
4996
|
rejectBackendStart?.(
|
|
4997
4997
|
new Error(
|
|
4998
|
-
`
|
|
4998
|
+
`Claw Agent backend exited before it became ready (${signal || code}). Log: ${DESKTOP_LOG_PATH}\n${recentHermesLog()}`
|
|
4999
4999
|
)
|
|
5000
5000
|
)
|
|
5001
5001
|
}
|
|
5002
5002
|
})
|
|
5003
5003
|
|
|
5004
|
-
await advanceBootProgress('backend.port', 'Waiting for
|
|
5004
|
+
await advanceBootProgress('backend.port', 'Waiting for Claw Agent backend to launch', 86)
|
|
5005
5005
|
// Discover the ephemeral port the child bound to
|
|
5006
5006
|
const port = await Promise.race([waitForDashboardPort(hermesProcess), backendStartFailed])
|
|
5007
5007
|
|
|
5008
5008
|
const baseUrl = `http://127.0.0.1:${port}`
|
|
5009
|
-
await advanceBootProgress('backend.wait', 'Waiting for
|
|
5009
|
+
await advanceBootProgress('backend.wait', 'Waiting for Claw Agent backend to become ready', 90)
|
|
5010
5010
|
await Promise.race([waitForHermes(baseUrl, token), backendStartFailed])
|
|
5011
5011
|
backendReady = true
|
|
5012
5012
|
const authToken = await adoptServedDashboardToken(baseUrl, token, {
|
|
@@ -5016,7 +5016,7 @@ async function startHermes() {
|
|
|
5016
5016
|
})
|
|
5017
5017
|
updateBootProgress({
|
|
5018
5018
|
phase: 'backend.ready',
|
|
5019
|
-
message: '
|
|
5019
|
+
message: 'Claw Agent backend is ready. Finalizing desktop startup',
|
|
5020
5020
|
progress: 94,
|
|
5021
5021
|
running: true,
|
|
5022
5022
|
error: null
|
|
@@ -5097,7 +5097,7 @@ function spawnSecondaryWindow({ sessionId, watch, newSession } = {}) {
|
|
|
5097
5097
|
height: SESSION_WINDOW_MIN_HEIGHT,
|
|
5098
5098
|
minWidth: SESSION_WINDOW_MIN_WIDTH,
|
|
5099
5099
|
minHeight: SESSION_WINDOW_MIN_HEIGHT,
|
|
5100
|
-
title:
|
|
5100
|
+
title: APP_NAME,
|
|
5101
5101
|
titleBarStyle: 'hidden',
|
|
5102
5102
|
titleBarOverlay: getTitleBarOverlayOptions(),
|
|
5103
5103
|
trafficLightPosition: IS_MAC ? WINDOW_BUTTON_POSITION : undefined,
|
|
@@ -5161,7 +5161,7 @@ function createWindow() {
|
|
|
5161
5161
|
height: 800,
|
|
5162
5162
|
minWidth: 400,
|
|
5163
5163
|
minHeight: 620,
|
|
5164
|
-
title:
|
|
5164
|
+
title: APP_NAME,
|
|
5165
5165
|
// Frameless title bar on every platform so the renderer can paint the
|
|
5166
5166
|
// "hide sidebar" button (and other left-side titlebar tools) flush with
|
|
5167
5167
|
// the top edge — matching the macOS layout where the traffic lights sit
|
|
@@ -5307,7 +5307,7 @@ ipcMain.handle('hermes:connection:revalidate', async () => {
|
|
|
5307
5307
|
// Unreachable remote: drop the stale cache so the renderer's next reconnect
|
|
5308
5308
|
// tick rebuilds a fresh, reachable descriptor. resetHermesConnection only
|
|
5309
5309
|
// nulls connectionPromise for a remote (no child to SIGTERM).
|
|
5310
|
-
rememberLog('Cached remote
|
|
5310
|
+
rememberLog('Cached remote Claw Agent backend failed liveness probe; dropping stale connection.')
|
|
5311
5311
|
resetHermesConnection()
|
|
5312
5312
|
return { ok: true, rebuilt: true }
|
|
5313
5313
|
}
|
|
@@ -5632,7 +5632,7 @@ ipcMain.handle('hermes:notify', (_event, payload) => {
|
|
|
5632
5632
|
// and the body click still works.
|
|
5633
5633
|
const actions = Array.isArray(payload?.actions) ? payload.actions : []
|
|
5634
5634
|
const notification = new Notification({
|
|
5635
|
-
title: payload?.title ||
|
|
5635
|
+
title: payload?.title || APP_NAME,
|
|
5636
5636
|
body: payload?.body || '',
|
|
5637
5637
|
silent: Boolean(payload?.silent),
|
|
5638
5638
|
actions: actions.map(action => ({ type: 'button', text: String(action?.text || '') }))
|
|
@@ -6043,7 +6043,7 @@ ipcMain.handle('hermes:fs:worktrees', async (_event, cwds) => worktreesForIpc(cw
|
|
|
6043
6043
|
|
|
6044
6044
|
ipcMain.handle('hermes:terminal:start', async (event, payload = {}) => {
|
|
6045
6045
|
if (!nodePty) {
|
|
6046
|
-
throw new Error('PTY support is unavailable. Reinstall desktop dependencies and restart
|
|
6046
|
+
throw new Error('PTY support is unavailable. Reinstall desktop dependencies and restart Claw Agent.')
|
|
6047
6047
|
}
|
|
6048
6048
|
|
|
6049
6049
|
ensureSpawnHelperExecutable()
|
|
@@ -6165,7 +6165,7 @@ function showAboutPanelFresh() {
|
|
|
6165
6165
|
app.setAboutPanelOptions({
|
|
6166
6166
|
applicationName: APP_NAME,
|
|
6167
6167
|
applicationVersion: resolveHermesVersion(),
|
|
6168
|
-
copyright: 'Copyright © 2026 Nous Research'
|
|
6168
|
+
copyright: 'Copyright © 2026 Nous Research · ClawPump distribution'
|
|
6169
6169
|
})
|
|
6170
6170
|
app.showAboutPanel()
|
|
6171
6171
|
}
|
|
@@ -6274,7 +6274,7 @@ async function runDesktopUninstall(mode) {
|
|
|
6274
6274
|
return {
|
|
6275
6275
|
ok: false,
|
|
6276
6276
|
error: 'agent-missing',
|
|
6277
|
-
message: `Can't run the uninstaller: no
|
|
6277
|
+
message: `Can't run the uninstaller: no Claw Agent venv at ${VENV_ROOT}.`
|
|
6278
6278
|
}
|
|
6279
6279
|
}
|
|
6280
6280
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<link rel="icon" type="image/png" sizes="180x180" href="/apple-touch-icon.png" />
|
|
9
9
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
10
10
|
<link rel="shortcut icon" href="/apple-touch-icon.png" />
|
|
11
|
-
<title>
|
|
11
|
+
<title>Claw Agent</title>
|
|
12
12
|
<script>
|
|
13
13
|
// Pre-paint the themed background before the app bundle loads. Without
|
|
14
14
|
// this, the first frame (which is what `ready-to-show` waits for) is the
|