@flame0510/project-aether 1.2.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 (102) hide show
  1. package/README.md +3 -1
  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 +316 -0
  9. package/app/agents/PageClient.tsx +708 -167
  10. package/app/agents/UpdateSection.tsx +300 -0
  11. package/app/agents/create/PageClient.tsx +11 -49
  12. package/app/agents/create/page.tsx +8 -21
  13. package/app/api/agents/[id]/backup/route.ts +26 -69
  14. package/app/api/agents/[id]/channels/pairing/route.ts +3 -3
  15. package/app/api/agents/[id]/channels/telegram/route.ts +2 -2
  16. package/app/api/agents/[id]/cold-backup/route.ts +56 -0
  17. package/app/api/agents/[id]/devices/route.ts +126 -0
  18. package/app/api/agents/[id]/invite-link/route.ts +53 -0
  19. package/app/api/agents/[id]/lifecycle/route.ts +3 -0
  20. package/app/api/agents/[id]/model/route.ts +113 -0
  21. package/app/api/agents/[id]/open-control-ui/route.ts +58 -0
  22. package/app/api/agents/[id]/recreate/route.ts +33 -187
  23. package/app/api/agents/[id]/restart/route.ts +5 -0
  24. package/app/api/agents/[id]/restore/route.ts +40 -70
  25. package/app/api/agents/[id]/route.ts +38 -169
  26. package/app/api/agents/[id]/update/rollback/route.ts +30 -0
  27. package/app/api/agents/[id]/update/route.ts +50 -0
  28. package/app/api/agents/activity-summary/route.ts +67 -0
  29. package/app/api/agents/create/route.ts +91 -145
  30. package/app/api/agents/devices-summary/route.ts +37 -0
  31. package/app/api/agents/download-image/route.ts +16 -9
  32. package/app/api/agents/image-status/route.ts +31 -111
  33. package/app/api/agents/models-summary/route.ts +163 -0
  34. package/app/api/agents/route.ts +25 -49
  35. package/app/api/agents/token/route.ts +33 -10
  36. package/app/api/assistant/route.ts +37 -16
  37. package/app/api/gateway/agent/route.ts +37 -6
  38. package/app/api/gateway/provider/balance/route.ts +5 -2
  39. package/app/api/gateway/provider/keys.ts +13 -1
  40. package/app/api/gateway/provider/route.ts +43 -12
  41. package/app/api/gateway/sync.ts +335 -76
  42. package/app/api/models/route.ts +28 -34
  43. package/app/api/provider/auth.ts +65 -0
  44. package/app/api/provider/upstream.ts +9 -2
  45. package/app/api/provider/v1/chat/completions/route.ts +22 -16
  46. package/app/api/provider/v1/models/route.ts +26 -133
  47. package/app/api/setup/agent-image/route.ts +14 -42
  48. package/app/components/DashboardToolbar.tsx +1 -1
  49. package/app/components/PulseChat.tsx +25 -39
  50. package/app/components/ui/RemoveButton.tsx +46 -0
  51. package/app/components/ui/Select.tsx +3 -2
  52. package/app/components/ui/index.ts +1 -0
  53. package/app/credentials/PageClient.tsx +2 -2
  54. package/app/gateway/PageClient.tsx +253 -674
  55. package/app/globals.css +8 -0
  56. package/app/lib/models-context.tsx +43 -7
  57. package/app/wizard/useWizard.ts +6 -1
  58. package/bin/rev4a.js +116 -50
  59. package/daemon.js +6 -6
  60. package/docs/ARCHITECTURE.md +110 -12
  61. package/docs/FRONTEND-ARCHITECTURE.md +31 -2
  62. package/docs/REV4A.md +93 -33
  63. package/docs/dev/API-REFERENCE.md +723 -178
  64. package/docs/dev/DATABASE.md +96 -0
  65. package/docs/dev/GATEWAY.md +250 -93
  66. package/docs/dev/PROVIDERS.md +26 -13
  67. package/docs/rag/DATA-FRESHNESS.md +59 -28
  68. package/docs/rag/GLOSSARY.md +27 -16
  69. package/docs/rag/REV4A-OVERVIEW.md +37 -25
  70. package/docs/rag/WHAT-I-CAN-ANSWER.md +10 -8
  71. package/instrumentation.ts +52 -1
  72. package/lib/agent-busy.ts +21 -0
  73. package/lib/agent-devices.ts +361 -0
  74. package/lib/agent-edit-state.ts +108 -0
  75. package/lib/agent-edit.ts +157 -0
  76. package/lib/agent-images.ts +375 -0
  77. package/lib/agent-ports-server.ts +27 -0
  78. package/lib/agent-ports.ts +68 -0
  79. package/lib/agent-readiness.ts +110 -0
  80. package/lib/agent-recreate-state.ts +108 -0
  81. package/lib/agent-recreate.ts +305 -0
  82. package/lib/agent-restore-state.ts +107 -0
  83. package/lib/agent-restore.ts +135 -0
  84. package/lib/agent-setup.ts +66 -17
  85. package/lib/agent-update-state.ts +122 -0
  86. package/lib/agent-update.ts +448 -0
  87. package/lib/agent-versions.json +14 -0
  88. package/lib/agent-versions.ts +80 -0
  89. package/lib/buildAgentImage.ts +88 -290
  90. package/lib/channelManager.ts +153 -64
  91. package/lib/cold-backup.ts +354 -0
  92. package/lib/container-file.ts +27 -0
  93. package/lib/credentials/delivery.ts +3 -3
  94. package/lib/db-bootstrap.mjs +76 -0
  95. package/lib/docker-utils.ts +3 -3
  96. package/lib/model-catalogue.ts +140 -27
  97. package/lib/provider-balance.ts +33 -12
  98. package/lib/rev4a-paths.ts +0 -21
  99. package/model-pricing.json +118 -110
  100. package/models.config.json +27 -12
  101. package/package.json +1 -1
  102. package/app/api/gateway/route.ts +0 -191
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - **Multi-container agent management** — Create, monitor, and manage OpenClaw agents in isolated Docker containers from a single UI
13
13
  - **Agent creation wizard** — Pick a template (Atlas, Prometheus, Argus), choose an LLM model, deploy an agent in seconds
14
14
  - **Provider sync** — Add API keys once, push them to all agent containers with one click
15
- - **Model configuration** — Change the primary model per agent, for every major provider (OpenAI, Anthropic, DeepSeek, Gemini, Ollama, etc.)
15
+ - **Model configuration** — Change the primary model and fallbacks per agent, from the detail panel on the Agents page. Providers: OpenAI, Anthropic, Google, DeepSeek, Groq, Kimi, GLM, Qwen, plus OpenRouter as an aggregator.
16
16
  - **Shared gateway token** — One token to rule all agents. Manage from the dashboard, sync to all containers
17
17
  - **Live event stream** — Real-time session lineage, agent spawning, cost reporting
18
18
  - **Workspace browser** — Browse and edit agent files live from the dashboard
@@ -81,6 +81,8 @@ already filled in — you normally never set these by hand. See
81
81
  | `REV4A_DATA_DIR` | `~/.config/rev4a` | No | Root of all persistent data |
82
82
  | `REV4A_DB` | `<data dir>/data/events.db` | No | SQLite database path |
83
83
  | `REV4A_WORKSPACE_ROOT` | — | No | Root exposed by the local file explorer |
84
+ | `REV4A_AGENT_IMAGE_REGISTRY` | `ghcr.io/flame0510/rev4a/openclaw-agent-base` | No | Registry repository agent images are pulled from, as `<repository>:<OpenClaw version>`. A `localhost` registry is reached over plain HTTP |
85
+ | `REV4A_DOCKER_WAIT_SECONDS` | `90` | No | How long `rev4a serve` waits for the Docker daemon at start-up before starting without it. Capped at 600; `0` checks once; a negative or non-numeric value means 90. Read from the process environment, not from the `.env` file |
84
86
 
85
87
  ---
86
88
 
@@ -1,37 +1,57 @@
1
1
  # Agent Templates
2
2
 
3
- ## Base Image: `openclaw-agent-base`
3
+ ## Base image: `openclaw-agent-base`
4
4
 
5
- The base image for all agent containers in the Rev4a ecosystem.
5
+ The image every agent container runs, built from
6
+ [`base-image/Dockerfile`](base-image/Dockerfile).
6
7
 
7
- ### Traefik Integration
8
+ It contains Node 24, OpenClaw, `gh`, `vercel`, `trello`, `supabase`, the plugins
9
+ shipped with the image under `/opt/openclaw-plugins` (DuckDuckGo web search), and
10
+ the entrypoint. The OpenClaw release is the `OPENCLAW_VERSION` build argument, also
11
+ recorded as the `org.opencontainers.image.version` label:
8
12
 
9
- The image includes the minimum static labels required by Traefik:
10
- - `traefik.enable=true`
13
+ ```bash
14
+ docker image inspect openclaw-agent-base:<tag> \
15
+ --format '{{index .Config.Labels "org.opencontainers.image.version"}}'
16
+ ```
17
+
18
+ The agent's persistent volume is mounted at `/root`. Anything the image puts under
19
+ `/root` reaches only agents created on an empty volume. That is why plugins live
20
+ under `/opt`, why the entrypoint applies the image defaults on every boot, and why
21
+ Rev4a re-applies its own settings at create and recreate. Entrypoint behaviour:
22
+ [docs/REV4A.md](../docs/REV4A.md#agent-templates).
11
23
 
12
- Dynamic labels (router rule, hostname, certresolver) must be passed at
13
- `docker run` time. The Rev4a agent creation wizard (`/agents/create`)
14
- generates them automatically.
24
+ ### Access
15
25
 
16
- ### Required Traefik Labels
26
+ An agent's Control UI is reached on its published host port,
27
+ `http://<host>:<port>`, with the gateway token. The Agents page opens it with a
28
+ one-time link that also approves the browser; any other browser needs approval once
29
+ in the agent's Browser access section.
17
30
 
18
- When creating an agent manually with `docker run`, add these labels:
31
+ Rev4a sets no reverse-proxy labels. For HTTPS or a domain, put your own reverse
32
+ proxy in front of the published port.
33
+
34
+ ### Creating an agent by hand
35
+
36
+ Use the creation wizard (`/agents/create`): it copies the template files, mounts
37
+ the shared volumes, writes the model and provider configuration and waits for the
38
+ gateway. A bare container, for testing the image only:
19
39
 
20
40
  ```bash
21
41
  docker run -d \
22
- --name agent-<id> \
23
- --network openclaw-core_default \
42
+ --name <id> \
43
+ --network rev4a-network \
24
44
  --restart unless-stopped \
45
+ --add-host host.docker.internal:host-gateway \
46
+ -v agent-<id>-data:/root \
25
47
  -l "AGENT_ID=<id>" \
26
- -l "traefik.enable=true" \
27
- -l "traefik.http.routers.agent-<id>.entrypoints=websecure" \
28
- -l "traefik.http.routers.agent-<id>.rule=Host(\`<id>.<your-domain>\`)" \
29
- -l "traefik.http.routers.agent-<id>.tls.certresolver=letsencrypt" \
30
- -l "traefik.http.services.agent-<id>.loadbalancer.server.port=3000" \
31
- -e AGENT_ID=<id> \
32
- openclaw-agent-base:latest
48
+ -e OPENCLAW_GATEWAY_TOKEN=<token> \
49
+ -p <port>:3000 \
50
+ openclaw-agent-base:<tag>
33
51
  ```
34
52
 
35
- After creation:
36
- - The agent is reachable at `https://<agent_id>.<your-domain>`
37
- - The Rev4a Agents page shows it with the Traefik link and a copyable token
53
+ ## Template directories
54
+
55
+ Each subdirectory (`atlas/`, `argus/`, `prometheus/`, `custom/`) holds the files
56
+ copied into a new agent's workspace. See
57
+ [docs/REV4A.md](../docs/REV4A.md#template-directories).
@@ -1,42 +1,30 @@
1
1
  # OpenClaw Agent Base Image
2
2
  #
3
3
  # Base image for agent containers in the Rev4a ecosystem.
4
- # Includes: Node, OpenClaw, gh CLI, Vercel CLI, Trello CLI, entrypoint.
4
+ # Includes: Node, OpenClaw, gh CLI, Vercel CLI, Trello CLI, Supabase CLI, entrypoint.
5
5
  #
6
- # Traefik integration:
7
- # - Static enablement is baked into the image.
8
- # - Dynamic router/service labels are provided
9
- # at container creation time via `docker run -l ...`.
10
- # - See agent-templates/README.md for the full label set.
6
+ # The agent's persistent volume is mounted at /root. Whatever this image writes
7
+ # under /root (openclaw.json, installed plugins) reaches only agents created on an
8
+ # empty volume. Settings every agent must have are re-applied by Rev4a at create
9
+ # and recreate (lib/agent-setup.ts); the config patches below only seed new volumes.
11
10
  #
12
11
  # Build:
13
- # docker build -t openclaw-agent-base:latest -f Dockerfile .
12
+ # docker build -t openclaw-agent-base:2026.9.3 -f Dockerfile .
13
+ # docker build --build-arg OPENCLAW_VERSION=<version> -t openclaw-agent-base:<version> .
14
14
  #
15
15
  # Test:
16
- # docker run --rm openclaw-agent-base:latest openclaw --version
17
- #
18
- # Run with Traefik support:
19
- # docker run -d \
20
- # --name agent-<id> \
21
- # --network openclaw-core_default \
22
- # --restart unless-stopped \
23
- # -l "AGENT_ID=<id>" \
24
- # -l "traefik.enable=true" \
25
- # -l "traefik.http.routers.agent-<id>.entrypoints=websecure" \
26
- # -l "traefik.http.routers.agent-<id>.rule=Host(`<id>.<your-domain>`)" \
27
- # -l "traefik.http.routers.agent-<id>.tls.certresolver=letsencrypt" \
28
- # -l "traefik.http.services.agent-<id>.loadbalancer.server.port=3000" \
29
- # -e AGENT_ID=<id> \
30
- # openclaw-agent-base:latest
16
+ # docker run --rm openclaw-agent-base:2026.9.3 openclaw --version
31
17
 
32
18
  FROM node:24-bookworm-slim
33
19
 
20
+ # The OpenClaw release this image runs, also recorded as a label so it can be read
21
+ # with `docker image inspect` without starting a container.
22
+ ARG OPENCLAW_VERSION=2026.9.3
23
+ ARG SUPABASE_VERSION=2.109.1
24
+
34
25
  LABEL description="OpenClaw Agent Base — Rev4a multicontainer ecosystem"
35
26
  LABEL maintainer="Rev4a Agency"
36
-
37
- # Traefik static labels — enable proxy support by default.
38
- # Dynamic router/service labels must be provided at container creation.
39
- LABEL traefik.enable="true"
27
+ LABEL org.opencontainers.image.version="${OPENCLAW_VERSION}"
40
28
 
41
29
  # Avoid interactive prompts
42
30
  ENV DEBIAN_FRONTEND=noninteractive
@@ -55,15 +43,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
55
43
  && apt-get update && apt-get install -y --no-install-recommends gh \
56
44
  && rm -rf /var/lib/apt/lists/*
57
45
 
58
- # Agent containers route model requests through Rev4a provider proxy
59
- RUN npm install -g openclaw@2026.7.1-2 vercel trello-cli \
60
- # Supabase CLI — installed via official GitHub release (npm global no longer supported on Linux)
61
- # Pinned to v2.109.1. Bump the version here to update.
62
- && curl -fsSL https://github.com/supabase/cli/releases/download/v2.109.1/supabase_linux_amd64.tar.gz -o /tmp/supabase.tar.gz \
46
+ # Agent containers route model requests through Rev4a provider proxy.
47
+ #
48
+ # --allow-scripts=openclaw: npm 11.16+ blocks install scripts unless allowed, and
49
+ # without them the OpenClaw install is silently incomplete (native prebuilds and
50
+ # bundled-plugin postinstall steps never run).
51
+ #
52
+ # Supabase CLI comes from its GitHub release (npm global is not supported on Linux),
53
+ # for the architecture being built: amd64 in CI, arm64 on an Apple Silicon machine.
54
+ #
55
+ # gateway.controlUi.dangerouslyDisableDeviceAuth is not set: OpenClaw 9.x retired
56
+ # it, ignores it and `doctor --fix` removes it. Browsers pair through device approval.
57
+ RUN npm install -g "openclaw@${OPENCLAW_VERSION}" vercel trello-cli --allow-scripts=openclaw \
58
+ && ARCH="$(dpkg --print-architecture)" \
59
+ && curl -fsSL "https://github.com/supabase/cli/releases/download/v${SUPABASE_VERSION}/supabase_linux_${ARCH}.tar.gz" -o /tmp/supabase.tar.gz \
63
60
  && tar xzf /tmp/supabase.tar.gz -C /usr/local/bin supabase \
64
61
  && chmod +x /usr/local/bin/supabase \
65
62
  && rm /tmp/supabase.tar.gz \
66
- && echo '{"gateway":{"mode":"local","controlUi":{"dangerouslyAllowHostHeaderOriginFallback":true,"dangerouslyDisableDeviceAuth":true}}}' \
63
+ && echo '{"gateway":{"mode":"local","controlUi":{"dangerouslyAllowHostHeaderOriginFallback":true}}}' \
67
64
  | openclaw config patch --stdin
68
65
 
69
66
  # Default config directory + shared-skills extraDirs pre-wired.
@@ -78,6 +75,19 @@ RUN mkdir -p /data/.openclaw \
78
75
  && echo '{"hooks":{"internal":{"entries":{"bootstrap-extra-files":{"enabled":true,"paths":[".rev4a/*.md"]}}}}}' \
79
76
  | openclaw config patch --stdin
80
77
 
78
+ # Plugins shipped with the image live outside /root, where the agent volume cannot
79
+ # hide them, so they reach existing agents too. Each is pinned to the OpenClaw
80
+ # release: a newer plugin can require a newer plugin API, and OpenClaw then skips it
81
+ # at load. Install scripts stay disabled, as `openclaw plugins install` does.
82
+ # The entrypoint adds these directories to plugins.load.paths.
83
+ #
84
+ # duckduckgo: key-free web_search provider. OpenClaw never auto-selects it, so the
85
+ # entrypoint sets it as the provider when none is configured.
86
+ RUN mkdir -p /opt/openclaw-plugins \
87
+ && cd /opt/openclaw-plugins \
88
+ && npm install --no-save --ignore-scripts "@openclaw/duckduckgo-plugin@${OPENCLAW_VERSION}" \
89
+ && test -f node_modules/@openclaw/duckduckgo-plugin/openclaw.plugin.json
90
+
81
91
  # Standard entrypoint
82
92
  COPY entrypoint.sh /agent-entrypoint.sh
83
93
  RUN chmod +x /agent-entrypoint.sh
@@ -87,4 +97,3 @@ EXPOSE 3000
87
97
 
88
98
  ENTRYPOINT ["/agent-entrypoint.sh"]
89
99
  CMD ["openclaw", "gateway", "--bind", "lan", "--port", "3000"]
90
-
@@ -1,9 +1,10 @@
1
1
  #!/bin/bash
2
2
  # Agent Container Entrypoint
3
3
  #
4
- # Resolves the gateway auth token and starts the OpenClaw gateway.
5
- # controlUi settings (dangerouslyUseHostHeaderOrigin, dangerouslyDisableDeviceAuth)
6
- # are baked into openclaw.json during the image build — this script does NOT touch it.
4
+ # Resolves the gateway auth token, applies OpenClaw's migrations after a version
5
+ # change, and starts the OpenClaw gateway. The gateway settings baked in at build
6
+ # time (gateway.mode, controlUi Host-header origin fallback) live in openclaw.json;
7
+ # this script does not edit that file.
7
8
 
8
9
  set -e
9
10
 
@@ -19,18 +20,72 @@ if [ -z "$AGENT_TOKEN" ]; then
19
20
  AGENT_TOKEN=$(openssl rand -hex 16 2>/dev/null || echo "agent-$(date +%s)-$$-${RANDOM}")
20
21
  fi
21
22
 
22
- # Run OpenClaw safe migrations only when the version changed since last boot
23
- # (i.e. after the image was rebuilt to a newer OpenClaw). This aligns the
24
- # persistent volume's state/config to the binary shipped in the image.
25
- # --non-interactive applies only safe migrations and SKIPS restart/service
26
- # actions, so it never restarts the gateway from under us. Runs BEFORE the
27
- # gateway starts, while state is at rest. Best-effort: never blocks boot.
23
+ # Apply OpenClaw's migrations when the version changed since the last boot: after
24
+ # the image moved to a newer OpenClaw, or on a new volume. Runs before the gateway
25
+ # starts, while state is at rest.
26
+ #
27
+ # `--fix` is required. From 2026.9.x the agent database schema is migrated only by
28
+ # `doctor --fix` with the gateway stopped; the gateway refuses the old schema rather
29
+ # than migrating it. `--non-interactive` applies the supported migrations and skips
30
+ # restart and service actions, so it never restarts the gateway from under us.
31
+ #
32
+ # The version is recorded only after a successful run, so a failed migration is
33
+ # retried on the next boot instead of being skipped for good. A failure does not
34
+ # block boot: the gateway starts and reports the problem itself.
28
35
  CUR_VER=$(openclaw --version 2>/dev/null || true)
29
36
  PREV_VER=$(cat /root/.openclaw/.last-version 2>/dev/null || true)
30
37
  if [ -n "$CUR_VER" ] && [ "$CUR_VER" != "$PREV_VER" ]; then
31
- echo "[entrypoint] OpenClaw version changed ('${PREV_VER}' -> '${CUR_VER}') — running safe migrations"
32
- openclaw doctor --non-interactive || echo "[entrypoint] doctor returned non-zero (ignored)"
33
- mkdir -p /root/.openclaw && printf '%s' "$CUR_VER" > /root/.openclaw/.last-version
38
+ echo "[entrypoint] OpenClaw version changed ('${PREV_VER}' -> '${CUR_VER}') — running migrations"
39
+ if openclaw doctor --fix --non-interactive; then
40
+ mkdir -p /root/.openclaw && printf '%s' "$CUR_VER" > /root/.openclaw/.last-version
41
+ else
42
+ echo "[entrypoint] doctor --fix failed; migrations will be retried on the next boot"
43
+ fi
44
+ fi
45
+
46
+ # Image defaults, applied before the gateway starts so none of them needs a restart.
47
+ # Each is written only when missing, so an explicit choice survives every boot and
48
+ # recreate:
49
+ # - plugins.load.paths includes the plugins shipped under /opt/openclaw-plugins
50
+ # - tools.web.search.provider is "duckduckgo" when no provider is set
51
+ # - agents.defaults.heartbeat.every is "0m" when unset: every heartbeat tick is a
52
+ # model-calling turn, so the recurring cadence is opt-in
53
+ #
54
+ # The config is read with one node process and written only through
55
+ # `openclaw config patch`, which validates. The plugin path goes in first: the
56
+ # provider is refused at validation unless its plugin is already discoverable.
57
+ # Nothing here blocks boot.
58
+ DDG_PLUGIN_DIR=/opt/openclaw-plugins/node_modules/@openclaw/duckduckgo-plugin
59
+ DEFAULTS=$(DDG_PLUGIN_DIR="$DDG_PLUGIN_DIR" node -e '
60
+ const fs = require("fs");
61
+ const dir = process.env.DDG_PLUGIN_DIR;
62
+ let cfg = {};
63
+ try {
64
+ const file = "/root/.openclaw/openclaw.json";
65
+ if (fs.existsSync(file)) cfg = JSON.parse(fs.readFileSync(file, "utf8"));
66
+ } catch {
67
+ console.log("unreadable");
68
+ process.exit(0);
69
+ }
70
+ const hasPlugin = fs.existsSync(dir + "/openclaw.plugin.json");
71
+ const paths = Array.isArray(cfg.plugins?.load?.paths) ? cfg.plugins.load.paths : [];
72
+ const out = {};
73
+ if (hasPlugin && !paths.includes(dir)) out.paths = { plugins: { load: { paths: [...paths, dir] } } };
74
+ if (hasPlugin && !cfg.tools?.web?.search?.provider) out.provider = { tools: { web: { search: { provider: "duckduckgo" } } } };
75
+ if (cfg.agents?.defaults?.heartbeat?.every === undefined) out.heartbeat = { agents: { defaults: { heartbeat: { every: "0m" } } } };
76
+ for (const [name, patch] of Object.entries(out)) console.log(name + " " + JSON.stringify(patch));
77
+ ' 2>/dev/null || echo "unreadable")
78
+
79
+ if [ "$DEFAULTS" = "unreadable" ]; then
80
+ echo "[entrypoint] openclaw.json could not be read as JSON; image defaults not applied"
81
+ elif [ -n "$DEFAULTS" ]; then
82
+ while read -r name patch; do
83
+ if printf '%s' "$patch" | openclaw config patch --stdin >/dev/null 2>&1; then
84
+ echo "[entrypoint] image default applied: $name"
85
+ else
86
+ echo "[entrypoint] image default not applied: $name (config patch refused it)"
87
+ fi
88
+ done <<< "$DEFAULTS"
34
89
  fi
35
90
 
36
91
  echo "[entrypoint] Starting gateway with auth token"