Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/web/src/app/(app)/claw/components/changelog-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export type ChangelogEntry = {

// Newest entries first. Developers add new entries to the top of this array.
export const CHANGELOG_ENTRIES: ChangelogEntry[] = [
{
date: '2026-05-01',
description: 'Updated OpenClaw to 2026.4.23.',
category: 'feature',
deployHint: 'redeploy_suggested',
},
{
date: '2026-04-28',
description:
Expand Down
2 changes: 1 addition & 1 deletion services/kiloclaw/.dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ FLY_IMAGE_DIGEST=
# Used by the worker to self-register the version → image tag mapping in KV,
# enabling per-user version tracking. Without this, version tracking fields
# will be null and instances fall back to FLY_IMAGE_TAG directly.
OPENCLAW_VERSION=2026.4.15
OPENCLAW_VERSION=2026.4.23

# Legacy fallback for existing instances without per-user apps.
# New instances get per-user apps (acct-{hash}) created automatically.
Expand Down
6 changes: 3 additions & 3 deletions services/kiloclaw/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ RUN npm install -g pnpm
# Patch: bump model discovery timeout from 5s to 60s — shared-cpu machines
# under boot load routinely need 27-35s for the first TLS+fetch to complete.
# Remove this sed patch once openclaw exposes a configurable timeout.
RUN npm install -g openclaw@2026.4.15 \
RUN npm install -g openclaw@2026.4.23 \
&& OC_DIST=/usr/local/lib/node_modules/openclaw/dist \
&& FOUND_FILES=$(find "$OC_DIST" -name 'provider-models-*.js' | wc -l | tr -d ' ') \
&& if [ "$FOUND_FILES" -eq 0 ]; then echo "ERROR: provider-models-*.js not found in openclaw dist" >&2; exit 1; fi \
Expand Down Expand Up @@ -228,8 +228,8 @@ RUN mkdir -p /root/.openclaw \
&& mkdir -p /root/clawd/skills

# Copy helper scripts (used at runtime by the controller/gateway)
# Build cache bust: 2026-04-24-v66-openclaw-2026.4.15-add-kilo-chat-plugin
RUN echo "12"
# Build cache bust: 2026-05-01-v67-openclaw-2026.4.23
RUN echo "13"
COPY openclaw-pairing-list.js /usr/local/bin/openclaw-pairing-list.js
COPY openclaw-device-pairing-list.js /usr/local/bin/openclaw-device-pairing-list.js

Expand Down
13 changes: 13 additions & 0 deletions services/kiloclaw/controller/src/config-writer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,7 @@ describe('generateBaseConfig', () => {
expect(config.hooks.enabled).toBe(true);
expect(config.hooks.token).toBe('test-hooks-token');
expect(config.hooks.path).toBe('/hooks');
expect(config.hooks.allowedSessionKeyPrefixes).toEqual(['hook:', 'inbound-email:']);
expect(config.hooks.presets).toBeUndefined();
expect(config.hooks.mappings).toContainEqual({
id: 'cloudflare-email-inbound',
Expand Down Expand Up @@ -1018,6 +1019,18 @@ describe('generateBaseConfig', () => {
expect(config.hooks.mappings).toContainEqual(
expect.objectContaining({ id: 'cloudflare-email-inbound', wakeMode: 'now' })
);
expect(config.hooks.allowedSessionKeyPrefixes).toEqual(['hook:', 'inbound-email:']);
});

it('preserves existing hook session key prefixes without duplicating inbound email', () => {
const existing = JSON.stringify({
hooks: { allowedSessionKeyPrefixes: ['custom:', 'hook:', 'inbound-email:'] },
});
const { deps } = fakeDeps(existing);
const env = { ...minimalEnv(), KILOCLAW_HOOKS_TOKEN: 'test-hooks-token' };
const config = generateBaseConfig(env, '/tmp/openclaw.json', deps);

expect(config.hooks.allowedSessionKeyPrefixes).toEqual(['custom:', 'hook:', 'inbound-email:']);
});

it('adds gmail preset when Gog credentials are configured', () => {
Expand Down
19 changes: 19 additions & 0 deletions services/kiloclaw/controller/src/config-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ type ConfigObject = Record<string, any>;
type EnvLike = Record<string, string | undefined>;

const INBOUND_EMAIL_HOOK_ID = 'cloudflare-email-inbound';
const DEFAULT_HOOK_SESSION_KEY_PREFIX = 'hook:';
const INBOUND_EMAIL_SESSION_KEY_PREFIX = 'inbound-email:';

function migrateHookMapping(mapping: ConfigObject): ConfigObject {
if (mapping.id === INBOUND_EMAIL_HOOK_ID) {
Expand Down Expand Up @@ -541,6 +543,23 @@ export function generateBaseConfig(
config.hooks.enabled = true;
config.hooks.token = env.KILOCLAW_HOOKS_TOKEN;
config.hooks.path = '/hooks';
config.hooks.allowedSessionKeyPrefixes = Array.isArray(config.hooks.allowedSessionKeyPrefixes)
? config.hooks.allowedSessionKeyPrefixes
: [];
if (
!(config.hooks.allowedSessionKeyPrefixes as string[]).includes(
DEFAULT_HOOK_SESSION_KEY_PREFIX
)
) {
(config.hooks.allowedSessionKeyPrefixes as string[]).push(DEFAULT_HOOK_SESSION_KEY_PREFIX);
}
if (
!(config.hooks.allowedSessionKeyPrefixes as string[]).includes(
INBOUND_EMAIL_SESSION_KEY_PREFIX
)
) {
(config.hooks.allowedSessionKeyPrefixes as string[]).push(INBOUND_EMAIL_SESSION_KEY_PREFIX);
}

config.hooks.mappings = Array.isArray(config.hooks.mappings)
? config.hooks.mappings.map((mapping: ConfigObject) => migrateHookMapping(mapping))
Expand Down
7 changes: 3 additions & 4 deletions services/kiloclaw/controller/src/pairing-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,9 @@ export const DEBOUNCE_DELAY_MS = 2_000;

export const FAILURE_RETRY_BASE_MS = 30_000;
export const FAILURE_RETRY_MAX_MS = 300_000;
// TEMPORARY: bumped from 45_000 to 180_000 because openclaw 2026.4.15 CLI
// startup takes ~65s (CPU profile shows ~55% in jiti normalizeAliases/createJiti
// from per-plugin loader churn). Revert to 45_000 once openclaw upstream
// reduces startup time. Tracking: <openclaw issue link TBD>.
// TEMPORARY: bumped from 45_000 to 180_000 because OpenClaw CLI startup can
// exceed 60s on shared-cpu instances. Revert to 45_000 once upstream startup
// is consistently below the old timeout on full KiloClaw images.
export const APPROVE_TIMEOUT_MS = 180_000;
export const CONFIG_PATH = '/root/.openclaw/openclaw.json';

Expand Down
4 changes: 2 additions & 2 deletions services/kiloclaw/e2e/docker-image-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ docker rm kiloclaw-gateway

```bash
# Check versions
docker run --rm kiloclaw:test node --version # v24.14.1
docker run --rm kiloclaw:test openclaw --version # 2026.4.15
docker run --rm kiloclaw:test node --version # v24.15.0
docker run --rm kiloclaw:test openclaw --version # 2026.4.23

# Check directories
docker run --rm kiloclaw:test ls -la /root/.openclaw
Expand Down
2 changes: 1 addition & 1 deletion services/kiloclaw/plugins/kilo-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"test": "vitest run"
},
"peerDependencies": {
"openclaw": "2026.4.15"
"openclaw": "2026.4.23"
},
"devDependencies": {
"esbuild": "^0.25.2",
Expand Down
12 changes: 6 additions & 6 deletions services/kiloclaw/src/durable-objects/kiloclaw-instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7303,9 +7303,9 @@ describe('applyPinnedVersion', () => {
});

(selectImageVersionForInstance as Mock).mockResolvedValueOnce({
openclawVersion: '2026.4.15',
openclawVersion: '2026.4.23',
variant: 'default',
imageTag: '2026-04-15',
imageTag: '2026-04-23',
imageDigest: 'sha256:latest',
publishedAt: new Date().toISOString(),
rolloutPercent: 100,
Expand All @@ -7314,11 +7314,11 @@ describe('applyPinnedVersion', () => {

const applied = await instance.applyPinnedVersion(null);

expect(applied.imageTag).toBe('2026-04-15');
expect(applied.openclawVersion).toBe('2026.4.15');
expect(applied.imageTag).toBe('2026-04-23');
expect(applied.openclawVersion).toBe('2026.4.23');
expect(selectImageVersionForInstance).toHaveBeenCalledOnce();
expect(resolveVersionByTag).not.toHaveBeenCalled();
expect(storage._store.get('trackedImageTag')).toBe('2026-04-15');
expect(storage._store.get('trackedImageTag')).toBe('2026-04-23');
});

it('when cleared, passes currentImageTag=null to the selector so non-cohort users can fall off the pinned candidate', async () => {
Expand All @@ -7335,7 +7335,7 @@ describe('applyPinnedVersion', () => {
// ignoreCurrentImageTag, it should instead be invoked with
// currentImageTag=null and return :latest.
(selectImageVersionForInstance as Mock).mockResolvedValueOnce({
openclawVersion: '2026.4.15',
openclawVersion: '2026.4.23',
variant: 'default',
imageTag: 'latest-tag',
imageDigest: 'sha256:latest',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ export async function approvePairingRequest(
'POST',
ControllerPairingApproveResponseSchema,
{ channel, code },
// TEMPORARY: 180s timeout — openclaw 2026.4.15 CLI startup is ~65s
// (jiti loader churn). Revert to default 30s once openclaw startup is fixed.
// TEMPORARY: 180s timeout while OpenClaw CLI startup can exceed 60s on
// shared-cpu instances. Revert once full-image startup is consistently fast.
{ timeoutMs: 180_000 }
);
} catch (error) {
Expand Down Expand Up @@ -389,8 +389,8 @@ export async function approveDevicePairingRequest(
'POST',
ControllerPairingApproveResponseSchema,
{ requestId },
// TEMPORARY: 180s timeout — openclaw 2026.4.15 CLI startup is ~65s
// (jiti loader churn). Revert to default 30s once openclaw startup is fixed.
// TEMPORARY: 180s timeout while OpenClaw CLI startup can exceed 60s on
// shared-cpu instances. Revert once full-image startup is consistently fast.
{ timeoutMs: 180_000 }
);
} catch (error) {
Expand Down
6 changes: 3 additions & 3 deletions services/kiloclaw/src/lib/rollout-bucket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('rolloutBucket', () => {
});

it('is deterministic for the same key', async () => {
const key = 'tag:kiloclaw-2026.4.15-abc:instance:550e8400-e29b-41d4-a716-446655440000';
const key = 'tag:kiloclaw-2026.4.23-abc:instance:550e8400-e29b-41d4-a716-446655440000';
const a = await rolloutBucket(key);
const b = await rolloutBucket(key);
expect(a).toBe(b);
Expand All @@ -37,8 +37,8 @@ describe('rolloutBucket', () => {

it('changes the bucket for a fixed instanceId when the salt (imageTag) changes', async () => {
const instanceId = '550e8400-e29b-41d4-a716-446655440001';
const a = await rolloutBucket(`tag:kiloclaw-2026.4.15-aaa:instance:${instanceId}`);
const b = await rolloutBucket(`tag:kiloclaw-2026.4.15-bbb:instance:${instanceId}`);
const a = await rolloutBucket(`tag:kiloclaw-2026.4.23-aaa:instance:${instanceId}`);
const b = await rolloutBucket(`tag:kiloclaw-2026.4.23-bbb:instance:${instanceId}`);
// Two different salts (rebuilds of the same upstream version) must produce
// independent draws so the same instance is not always the canary.
expect(a).not.toBe(b);
Expand Down
2 changes: 1 addition & 1 deletion services/kiloclaw/src/lib/version-rollout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function createJsonKV(): KVNamespace & { _store: Map<string, string> } {

function entry(imageTag: string, rolloutPercent: number, isLatest = false): ImageVersionEntry {
return {
openclawVersion: '2026.4.15',
openclawVersion: '2026.4.23',
variant: 'default',
imageTag,
imageDigest: null,
Expand Down