Skip to content
Merged
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
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@leadbay/core",
"version": "0.2.1",
"version": "0.2.2",
"private": true,
"description": "Leadbay shared core: HTTP client and protocol-agnostic tool definitions. Consumed by @leadbay/leadclaw and @leadbay/mcp.",
"type": "module",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/composite/refine-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const refinePrompt: Tool<RefinePromptParams> = {
would_call: {
method: "POST",
path: `/organizations/${orgId}/user_prompt`,
body: { prompt: params.prompt },
body: { user_prompt: params.prompt },
},
};
}
Expand All @@ -73,7 +73,7 @@ export const refinePrompt: Tool<RefinePromptParams> = {
const STALE_GUARD_MS = 5_000;

await client.requestVoid("POST", `/organizations/${orgId}/user_prompt`, {
prompt: params.prompt,
user_prompt: params.prompt,
});

// Cache invalidation — /me's computing_intelligence flag is now true.
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/tools/set-user-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ export const setUserPrompt: Tool<SetUserPromptParams> = {
would_call: {
method: "POST",
path: `/organizations/${orgId}/user_prompt`,
body: { prompt: params.prompt },
body: { user_prompt: params.prompt },
},
};
}
await client.requestVoid("POST", `/organizations/${orgId}/user_prompt`, {
prompt: params.prompt,
user_prompt: params.prompt,
});
// Mutates organization.computing_intelligence (and clears any pending
// clarification). The /me cache holds organization.computing_intelligence;
Expand Down
82 changes: 82 additions & 0 deletions packages/core/test/unit/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,88 @@ describe("Granular write tools invalidate /me cache", () => {
});
});

describe("user_prompt POST body shape (contract pin — #3508)", () => {
// Backend's UserPromptPayload is kotlinx.serialization @SerialName("user_prompt");
// strict deserialization rejects { prompt: ... }. These tests pin the wire key
// so the contract bug can't silently reappear.
it("setUserPrompt sends { user_prompt }, not { prompt }", async () => {
const { setUserPrompt } = await import("../../src/tools/set-user-prompt.js");
const { requests } = mockHttp([
{
method: "GET",
path: "/1.5/users/me",
status: 200,
body: {
id: "u",
organization: { id: "org-1", name: "X", computing_intelligence: false },
},
},
{ method: "POST", path: "/1.5/organizations/org-1/user_prompt", status: 204 },
]);
const client = new LeadbayClient(BASE, "u.test-token");
await setUserPrompt.execute(client, { prompt: "focus on hospitals" });
const post = requests.find(
(r) => r.method === "POST" && r.path === "/1.5/organizations/org-1/user_prompt"
);
expect(post?.body).toBeDefined();
const parsed = JSON.parse(post!.body!);
expect(parsed).toEqual({ user_prompt: "focus on hospitals" });
expect(parsed).not.toHaveProperty("prompt");
});

it("refinePrompt sends { user_prompt }, not { prompt }", async () => {
const { refinePrompt } = await import("../../src/composite/refine-prompt.js");
const { requests } = mockHttp([
{
method: "GET",
path: "/1.5/users/me",
status: 200,
body: {
id: "u",
admin: true,
organization: { id: "org-1", name: "X", computing_intelligence: false },
},
},
{ method: "POST", path: "/1.5/organizations/org-1/user_prompt", status: 204 },
]);
const client = new LeadbayClient(BASE, "u.test-token");
// clarification_poll_attempts: 0 skips the poll loop entirely.
await refinePrompt.execute(client, {
prompt: "focus on hospitals",
clarification_poll_attempts: 0,
});
const post = requests.find(
(r) => r.method === "POST" && r.path === "/1.5/organizations/org-1/user_prompt"
);
expect(post?.body).toBeDefined();
const parsed = JSON.parse(post!.body!);
expect(parsed).toEqual({ user_prompt: "focus on hospitals" });
expect(parsed).not.toHaveProperty("prompt");
});

it("refinePrompt dry_run preview uses user_prompt key", async () => {
const { refinePrompt } = await import("../../src/composite/refine-prompt.js");
mockHttp([
{
method: "GET",
path: "/1.5/users/me",
status: 200,
body: {
id: "u",
admin: true,
organization: { id: "org-1", name: "X" },
},
},
]);
const client = new LeadbayClient(BASE, "u.test-token");
const res: any = await refinePrompt.execute(client, {
prompt: "p",
dry_run: true,
});
expect(res.would_call.body).toEqual({ user_prompt: "p" });
});
});

describe("LeadbayClient.acquireSelectionLock — Mutex", () => {
it("serialises concurrent selection holders", async () => {
const client = new LeadbayClient(BASE, "u.test-token");
Expand Down
6 changes: 6 additions & 0 deletions packages/leadclaw/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog — @leadbay/leadclaw

## 0.2.3 — 2026-04-21

Bug fix release. Picks up `@leadbay/core@0.2.2` underneath.

- **Fix [product#3508](https://github.com/leadbay/product/issues/3508)**: `leadbay_refine_prompt` (and the granular `leadbay_set_user_prompt`) now send `{ user_prompt }` to `POST /organizations/{orgId}/user_prompt` instead of `{ prompt }`. The backend's `UserPromptPayload` uses `@SerialName("user_prompt")` with strict kotlinx.serialization, so the old key was rejected as a deserialization error (400). The `dry_run` preview was affected the same way. Version kept in sync with `@leadbay/mcp@0.2.3`.

## 0.2.2 — 2026-04-21

Bug fix + contract correction + mental-model docs release. Picks up `@leadbay/core@0.2.1` underneath.
Expand Down
2 changes: 1 addition & 1 deletion packages/leadclaw/openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"id": "leadclaw",
"name": "LeadClaw",
"description": "Leadbay for AI agents: a daily sales-lead inbox with firmographic + AI qualification layers, plus on-demand deeper qualification and contact enrichment. Each login delivers a fresh batch of leads paced by the user's recent consumption; the agent skims, deepens promising ones, and proposes outreach.",
"version": "0.2.2",
"version": "0.2.3",
"contracts": {
"tools": [
"leadbay_login",
Expand Down
2 changes: 1 addition & 1 deletion packages/leadclaw/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@leadbay/leadclaw",
"version": "0.2.2",
"version": "0.2.3",
"description": "OpenClaw plugin for Leadbay — AI lead discovery, qualification, and enrichment",
"type": "module",
"main": "dist/index.js",
Expand Down
6 changes: 6 additions & 0 deletions packages/mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog — @leadbay/mcp

## 0.2.3 — 2026-04-21

Bug fix release.

- **Fix [product#3508](https://github.com/leadbay/product/issues/3508)**: `leadbay_refine_prompt` (and the granular `leadbay_set_user_prompt`) now send the correct `{ user_prompt }` body key to `POST /organizations/{orgId}/user_prompt`. Previous versions sent `{ prompt }`, which the backend's strict kotlinx.serialization rejected with a JSON deserialization error (400). The `dry_run` preview for both tools was printing the wrong shape too, which hid the mismatch from anyone inspecting it. New unit tests pin the wire key so this contract can't silently regress again.

## 0.2.2 — 2026-04-21

Bug fix + contract correction + mental-model docs release.
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@leadbay/mcp",
"version": "0.2.2",
"version": "0.2.3",
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
"type": "module",
"bin": {
Expand Down
Loading