From 8708b702b055e01759f5ea26f22677c0c7ae735a Mon Sep 17 00:00:00 2001 From: Ankit Ranjan Date: Mon, 7 Sep 2026 16:43:48 +0530 Subject: [PATCH 1/2] feat: default site-memory seed lookup to the official cloud URL Webcmd now looks up https://api.webcmd.dev by default on first access to a product with no local memory, instead of requiring a configured WEBCMD_GLOBAL_MEMORY_URL. WEBCMD_GLOBAL_MEMORY_URL becomes a developer/test override; exact WEBCMD_GLOBAL_MEMORY=off opts out. Existing timeout, no-retry, credential omission, and no-refresh behavior are unchanged. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013KuPnjTXhrBoY2KtPV2R89 --- PRIVACY.md | 4 ++-- src/site-memory/seed-client.test.ts | 11 +++++++---- src/site-memory/seed-client.ts | 5 +++-- src/site-memory/self-learning.integration.test.ts | 2 +- src/skills.test.ts | 8 ++++---- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index b4b18bcd..de6210e8 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -10,9 +10,9 @@ Trace artifacts, cache files, plugins, user adapters, and site memory are stored ## Local site-memory seed lookup -`WEBCMD_GLOBAL_MEMORY_URL` enables a public unauthenticated GET `/v1/site-memory/seeds/` on first access when no local memory exists. The request uses a 2-second timeout and no retry. It discloses the resolved product/domain. +Webcmd defaults to a public unauthenticated GET `https://api.webcmd.dev/v1/site-memory/seeds/` on first access when no local product memory exists. The request discloses only the resolved product/domain; it sends no credentials, page contents, local memory, or candidate evidence. `WEBCMD_GLOBAL_MEMORY=off` disables the request. `WEBCMD_GLOBAL_MEMORY_URL` is only a developer/test override. -With no URL configured, learning is local-only and Webcmd makes no seed request. `WEBCMD_GLOBAL_MEMORY=off` disables even a configured URL. +The lookup uses a 2-second timeout and no retry, and never refreshes initialized memory. ## Candidate public-IP provenance diff --git a/src/site-memory/seed-client.test.ts b/src/site-memory/seed-client.test.ts index ab3ae9db..92a39af0 100644 --- a/src/site-memory/seed-client.test.ts +++ b/src/site-memory/seed-client.test.ts @@ -5,12 +5,15 @@ const urlEnv = { WEBCMD_GLOBAL_MEMORY_URL: 'https://api.webcmd.dev' }; describe('global seed client', () => { it.each([{}, { WEBCMD_GLOBAL_MEMORY_URL: ' ' }])( - 'does not fetch without a configured remote URL', + 'uses the official default URL without a non-empty override', async (env) => { - const fetch = vi.fn(); + const fetch = vi.fn(async (input: RequestInfo | URL) => { + expect(String(input)).toBe('https://api.webcmd.dev/v1/site-memory/seeds/example.test'); + return jsonResponse({ revision: 'seed-1', site: '# Example\n' }); + }); await expect(createHttpSeedProvider({ fetch, env }).lookup('example.test')) - .resolves.toEqual({ status: 'unattempted' }); - expect(fetch).not.toHaveBeenCalled(); + .resolves.toEqual({ status: 'available', revision: 'seed-1', site: '# Example\n' }); + expect(fetch).toHaveBeenCalledTimes(1); }, ); diff --git a/src/site-memory/seed-client.ts b/src/site-memory/seed-client.ts index adeb059e..b84aa910 100644 --- a/src/site-memory/seed-client.ts +++ b/src/site-memory/seed-client.ts @@ -5,6 +5,7 @@ export interface GlobalSeedProvider { } const LOOKUP_TIMEOUT_MS = 2000; +export const DEFAULT_GLOBAL_MEMORY_URL = 'https://api.webcmd.dev'; export function createHttpSeedProvider(options: { fetch?: typeof fetch; @@ -15,8 +16,8 @@ export function createHttpSeedProvider(options: { return { async lookup(productKey, signal) { - const baseUrl = env.WEBCMD_GLOBAL_MEMORY_URL?.trim(); - if (env.WEBCMD_GLOBAL_MEMORY === 'off' || !baseUrl) return { status: 'unattempted' }; + if (env.WEBCMD_GLOBAL_MEMORY === 'off') return { status: 'unattempted' }; + const baseUrl = env.WEBCMD_GLOBAL_MEMORY_URL?.trim() || DEFAULT_GLOBAL_MEMORY_URL; const base = baseUrl.replace(/\/+$/, ''); const url = `${base}/v1/site-memory/seeds/${encodeURIComponent(productKey)}`; diff --git a/src/site-memory/self-learning.integration.test.ts b/src/site-memory/self-learning.integration.test.ts index 904908b5..69c4edc4 100644 --- a/src/site-memory/self-learning.integration.test.ts +++ b/src/site-memory/self-learning.integration.test.ts @@ -75,7 +75,7 @@ describe('self-learning lifecycle', () => { url: 'https://local.test/', taskId: 'task-local', homeDir, - seedProvider: createHttpSeedProvider({ fetch: localFetch, env: {} }), + seedProvider: createHttpSeedProvider({ fetch: localFetch, env: { WEBCMD_GLOBAL_MEMORY: 'off' } }), }); expect(seeded.manifest?.seed).toEqual({ status: 'available', revision: 'seed-1' }); diff --git a/src/skills.test.ts b/src/skills.test.ts index 89e29307..f54c6b1a 100644 --- a/src/skills.test.ts +++ b/src/skills.test.ts @@ -542,10 +542,10 @@ describe('public copy', () => { expect(intro).toMatch(/except|seed lookup/i); expect(privacy).toMatch(/unauthenticated GET/i); expect(privacy).toContain('/v1/site-memory/seeds/'); - expect(privacy).not.toMatch(/default(?: base)?(?: is|:)[^\n]*api\.webcmd\.dev/i); - expect(privacy).toMatch(/WEBCMD_GLOBAL_MEMORY_URL[^\n]*(?:enables|configured)/i); - expect(privacy).toMatch(/absent|no URL|not configured|without a(?:n)?(?: configured)? URL/i); - expect(privacy).toMatch(/local-only|no request|does not (?:make|send|perform) (?:a |the )?seed/i); + expect(privacy).toMatch(/defaults?[^\n]*api\.webcmd\.dev/i); + expect(privacy).toMatch(/WEBCMD_GLOBAL_MEMORY_URL[^\n]*(?:developer|test)[^\n]*override/i); + expect(privacy).toMatch(/no local product memory/i); + expect(privacy).toMatch(/never refresh(?:es)? initialized memory/i); expect(privacy).toMatch(/2-second timeout|2 second timeout/i); expect(privacy).toMatch(/no retry/i); expect(privacy).toContain('WEBCMD_GLOBAL_MEMORY=off'); From 0c2188bcb813b6edde822c9cfeec6c202a71a2e3 Mon Sep 17 00:00:00 2001 From: Ankit Ranjan Date: Mon, 7 Sep 2026 18:20:58 +0530 Subject: [PATCH 2/2] fix: tolerate scheduler jitter in seed-client timeout test expect(Date.now() - started).toBeGreaterThanOrEqual(2000) flaked on macOS CI (measured 1999ms) against AbortSignal.timeout(2000)'s real timer. A few ms of slack is expected; the test's intent (aborts around two seconds, does not retry) is unaffected by a 10ms tolerance. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013KuPnjTXhrBoY2KtPV2R89 --- src/site-memory/seed-client.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/site-memory/seed-client.test.ts b/src/site-memory/seed-client.test.ts index 92a39af0..5dad5ead 100644 --- a/src/site-memory/seed-client.test.ts +++ b/src/site-memory/seed-client.test.ts @@ -81,7 +81,10 @@ describe('global seed client', () => { expect(result).toEqual({ status: 'lookup-failed' }); expect(calls).toBe(1); - expect(Date.now() - started).toBeGreaterThanOrEqual(2000); + // A few ms of scheduler slack under CI is expected: AbortSignal.timeout's + // internal timer can fire a hair before the full duration has elapsed + // relative to Date.now()'s millisecond sampling. + expect(Date.now() - started).toBeGreaterThanOrEqual(1990); expect(Date.now() - started).toBeLessThan(4000); });