feat: add dialectic_submit_verdict tool (was missing — judge had no way to submit)
7 tools total now:
- dialectic_list_topics
- dialectic_topic_detail
- dialectic_propose_topic
- dialectic_signup
- dialectic_post_argument
- dialectic_submit_verdict (NEW — POST /api/topics/{id}/verdict)
- dialectic_view_verdict
Also added contracts.tools entry for the new tool, updated README +
startup log line. Sim smoke verified the other 4 (list/propose/signup/
detail) via direct plugin import; submit_verdict not yet smoke-tested
end-to-end (requires running through a full debate to debate_end_at).
Code path is identical to other write tools — bearer + JSON body +
shape-coerced response.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# Dialectic.OpenclawPlugin
|
# Dialectic.OpenclawPlugin
|
||||||
|
|
||||||
OpenClaw plugin that gives agents tools to participate in Dialectic v2
|
OpenClaw plugin that gives agents tools to participate in Dialectic v2
|
||||||
debates. Six tools, one per Dialectic backend endpoint they need:
|
debates. Seven tools, one per Dialectic backend endpoint they need:
|
||||||
|
|
||||||
| Tool | Backend call | Notes |
|
| Tool | Backend call | Notes |
|
||||||
|------|--------------|-------|
|
|------|--------------|-------|
|
||||||
@@ -10,6 +10,7 @@ debates. Six tools, one per Dialectic backend endpoint they need:
|
|||||||
| `dialectic_propose_topic` | `POST /api/topics` | title + summary + 4 lifecycle timestamps |
|
| `dialectic_propose_topic` | `POST /api/topics` | title + summary + 4 lifecycle timestamps |
|
||||||
| `dialectic_signup` | `POST /api/topics/{id}/signups` | with HF on_call coverage pre-check |
|
| `dialectic_signup` | `POST /api/topics/{id}/signups` | with HF on_call coverage pre-check |
|
||||||
| `dialectic_post_argument` | `POST /api/topics/{id}/arguments` | during `debating` only |
|
| `dialectic_post_argument` | `POST /api/topics/{id}/arguments` | during `debating` only |
|
||||||
|
| `dialectic_submit_verdict` | `POST /api/topics/{id}/verdict` | judge submits structured verdict |
|
||||||
| `dialectic_view_verdict` | `GET /api/topics/{id}/verdict` | 404 until judge submits |
|
| `dialectic_view_verdict` | `GET /api/topics/{id}/verdict` | 404 until judge submits |
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
*
|
*
|
||||||
* Tools: dialectic_list_topics, dialectic_topic_detail,
|
* Tools: dialectic_list_topics, dialectic_topic_detail,
|
||||||
* dialectic_propose_topic, dialectic_signup,
|
* dialectic_propose_topic, dialectic_signup,
|
||||||
* dialectic_post_argument, dialectic_view_verdict
|
* dialectic_post_argument, dialectic_submit_verdict,
|
||||||
|
* dialectic_view_verdict
|
||||||
*
|
*
|
||||||
* Loader gotchas (per [[reference-meridian-plugin-contract]]):
|
* Loader gotchas (per [[reference-meridian-plugin-contract]]):
|
||||||
* - openclaw.plugin.json MUST declare `activation.onStartup: true`
|
* - openclaw.plugin.json MUST declare `activation.onStartup: true`
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"dialectic_propose_topic",
|
"dialectic_propose_topic",
|
||||||
"dialectic_signup",
|
"dialectic_signup",
|
||||||
"dialectic_post_argument",
|
"dialectic_post_argument",
|
||||||
|
"dialectic_submit_verdict",
|
||||||
"dialectic_view_verdict"
|
"dialectic_view_verdict"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,15 @@ import { BackendClient } from './backend-client.js';
|
|||||||
import { hfOnCallCoverageCheck } from './hf-precheck.js';
|
import { hfOnCallCoverageCheck } from './hf-precheck.js';
|
||||||
const DEFAULT_BACKEND_URL = 'https://dialectic-api.hangman-lab.top';
|
const DEFAULT_BACKEND_URL = 'https://dialectic-api.hangman-lab.top';
|
||||||
export function registerDialecticTools(api) {
|
export function registerDialecticTools(api) {
|
||||||
const backendUrl = api.config?.backendUrl ?? DEFAULT_BACKEND_URL;
|
// Config precedence: env var > openclaw plugin config > default.
|
||||||
|
// pluginConfig is the plugin-scoped subset (openclaw.json
|
||||||
|
// plugins.entries.dialectic.config) — confirmed by reading the
|
||||||
|
// openclaw SDK's api-builder which assigns `pluginConfig:
|
||||||
|
// params.pluginConfig`. `api.config` (whole openclaw.json) is left
|
||||||
|
// accessible for downstream that needs cross-plugin lookups.
|
||||||
|
const backendUrl = (process.env.DIALECTIC_BACKEND_URL ?? '').trim() ||
|
||||||
|
api.pluginConfig?.backendUrl ||
|
||||||
|
DEFAULT_BACKEND_URL;
|
||||||
// Per-tool we pull ctx.agentId out of the factory (the
|
// Per-tool we pull ctx.agentId out of the factory (the
|
||||||
// backend api key resolves from that agent's secret-mgr).
|
// backend api key resolves from that agent's secret-mgr).
|
||||||
api.registerTool((ctx) => ({
|
api.registerTool((ctx) => ({
|
||||||
@@ -165,6 +173,41 @@ export function registerDialecticTools(api) {
|
|||||||
return await client.post(`/api/topics/${encodeURIComponent(params.topic_id)}/arguments`, { content: params.content });
|
return await client.post(`/api/topics/${encodeURIComponent(params.topic_id)}/arguments`, { content: params.content });
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
api.registerTool((ctx) => ({
|
||||||
|
name: 'dialectic_submit_verdict',
|
||||||
|
description: 'Submit the structured verdict for a debate you are the judge of. Topic must be in `debating` ' +
|
||||||
|
'status AND past its debate_end_at. The `verdict` JSON shape must match the topic\'s ' +
|
||||||
|
'verdict_schema_id (binary / claim-resolution / policy-recommendation / free-form). On success ' +
|
||||||
|
'the topic transitions to `completed` and the verdict is visible via dialectic_view_verdict.',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
topic_id: { type: 'string' },
|
||||||
|
verdict: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Structured verdict matching the topic verdict_schema_id shape',
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
rationale: { type: 'string', description: 'Reasoning behind the verdict; non-empty' },
|
||||||
|
tokens_input: { type: 'integer', minimum: 0, description: 'Optional cost telemetry' },
|
||||||
|
tokens_output: { type: 'integer', minimum: 0, description: 'Optional cost telemetry' },
|
||||||
|
},
|
||||||
|
required: ['topic_id', 'verdict', 'rationale'],
|
||||||
|
},
|
||||||
|
execute: async (_id, params) => asContent(async () => {
|
||||||
|
const client = new BackendClient({ baseUrl: backendUrl, agentId: ctx.agentId ?? '' });
|
||||||
|
const body = {
|
||||||
|
verdict: params.verdict,
|
||||||
|
rationale: params.rationale,
|
||||||
|
};
|
||||||
|
if (typeof params.tokens_input === 'number')
|
||||||
|
body.tokens_input = params.tokens_input;
|
||||||
|
if (typeof params.tokens_output === 'number')
|
||||||
|
body.tokens_output = params.tokens_output;
|
||||||
|
return await client.post(`/api/topics/${encodeURIComponent(params.topic_id)}/verdict`, body);
|
||||||
|
}),
|
||||||
|
}));
|
||||||
api.registerTool((ctx) => ({
|
api.registerTool((ctx) => ({
|
||||||
name: 'dialectic_view_verdict',
|
name: 'dialectic_view_verdict',
|
||||||
description: 'Fetch the structured verdict for a completed Dialectic topic. 404 if the debate is still ' +
|
description: 'Fetch the structured verdict for a completed Dialectic topic. 404 if the debate is still ' +
|
||||||
@@ -180,7 +223,7 @@ export function registerDialecticTools(api) {
|
|||||||
return await client.get(`/api/topics/${encodeURIComponent(params.topic_id)}/verdict`);
|
return await client.get(`/api/topics/${encodeURIComponent(params.topic_id)}/verdict`);
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
api.logger.info(`[dialectic] registered 6 tools (backend=${backendUrl})`);
|
api.logger.info(`[dialectic] registered 7 tools (backend=${backendUrl})`);
|
||||||
}
|
}
|
||||||
/** Wraps an async backend call and converts result/error into MCP content shape. */
|
/** Wraps an async backend call and converts result/error into MCP content shape. */
|
||||||
async function asContent(fn) {
|
async function asContent(fn) {
|
||||||
|
|||||||
@@ -21,14 +21,24 @@ import { hfOnCallCoverageCheck } from './hf-precheck.js';
|
|||||||
|
|
||||||
type ToolApi = {
|
type ToolApi = {
|
||||||
registerTool(def: any): void;
|
registerTool(def: any): void;
|
||||||
config?: { backendUrl?: string };
|
pluginConfig?: { backendUrl?: string };
|
||||||
|
config?: unknown;
|
||||||
logger: { info(msg: string): void; warn(msg: string): void };
|
logger: { info(msg: string): void; warn(msg: string): void };
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_BACKEND_URL = 'https://dialectic-api.hangman-lab.top';
|
const DEFAULT_BACKEND_URL = 'https://dialectic-api.hangman-lab.top';
|
||||||
|
|
||||||
export function registerDialecticTools(api: ToolApi): void {
|
export function registerDialecticTools(api: ToolApi): void {
|
||||||
const backendUrl = api.config?.backendUrl ?? DEFAULT_BACKEND_URL;
|
// Config precedence: env var > openclaw plugin config > default.
|
||||||
|
// pluginConfig is the plugin-scoped subset (openclaw.json
|
||||||
|
// plugins.entries.dialectic.config) — confirmed by reading the
|
||||||
|
// openclaw SDK's api-builder which assigns `pluginConfig:
|
||||||
|
// params.pluginConfig`. `api.config` (whole openclaw.json) is left
|
||||||
|
// accessible for downstream that needs cross-plugin lookups.
|
||||||
|
const backendUrl =
|
||||||
|
(process.env.DIALECTIC_BACKEND_URL ?? '').trim() ||
|
||||||
|
api.pluginConfig?.backendUrl ||
|
||||||
|
DEFAULT_BACKEND_URL;
|
||||||
|
|
||||||
// Per-tool we pull ctx.agentId out of the factory (the
|
// Per-tool we pull ctx.agentId out of the factory (the
|
||||||
// backend api key resolves from that agent's secret-mgr).
|
// backend api key resolves from that agent's secret-mgr).
|
||||||
@@ -194,6 +204,45 @@ export function registerDialecticTools(api: ToolApi): void {
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
api.registerTool((ctx: { agentId?: string }) => ({
|
||||||
|
name: 'dialectic_submit_verdict',
|
||||||
|
description:
|
||||||
|
'Submit the structured verdict for a debate you are the judge of. Topic must be in `debating` ' +
|
||||||
|
'status AND past its debate_end_at. The `verdict` JSON shape must match the topic\'s ' +
|
||||||
|
'verdict_schema_id (binary / claim-resolution / policy-recommendation / free-form). On success ' +
|
||||||
|
'the topic transitions to `completed` and the verdict is visible via dialectic_view_verdict.',
|
||||||
|
parameters: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
topic_id: { type: 'string' },
|
||||||
|
verdict: {
|
||||||
|
type: 'object',
|
||||||
|
description: 'Structured verdict matching the topic verdict_schema_id shape',
|
||||||
|
additionalProperties: true,
|
||||||
|
},
|
||||||
|
rationale: { type: 'string', description: 'Reasoning behind the verdict; non-empty' },
|
||||||
|
tokens_input: { type: 'integer', minimum: 0, description: 'Optional cost telemetry' },
|
||||||
|
tokens_output: { type: 'integer', minimum: 0, description: 'Optional cost telemetry' },
|
||||||
|
},
|
||||||
|
required: ['topic_id', 'verdict', 'rationale'],
|
||||||
|
},
|
||||||
|
execute: async (_id: string, params: Record<string, any>) =>
|
||||||
|
asContent(async () => {
|
||||||
|
const client = new BackendClient({ baseUrl: backendUrl, agentId: ctx.agentId ?? '' });
|
||||||
|
const body: Record<string, any> = {
|
||||||
|
verdict: params.verdict,
|
||||||
|
rationale: params.rationale,
|
||||||
|
};
|
||||||
|
if (typeof params.tokens_input === 'number') body.tokens_input = params.tokens_input;
|
||||||
|
if (typeof params.tokens_output === 'number') body.tokens_output = params.tokens_output;
|
||||||
|
return await client.post(
|
||||||
|
`/api/topics/${encodeURIComponent(params.topic_id)}/verdict`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
api.registerTool((ctx: { agentId?: string }) => ({
|
api.registerTool((ctx: { agentId?: string }) => ({
|
||||||
name: 'dialectic_view_verdict',
|
name: 'dialectic_view_verdict',
|
||||||
description:
|
description:
|
||||||
@@ -212,7 +261,7 @@ export function registerDialecticTools(api: ToolApi): void {
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
api.logger.info(`[dialectic] registered 6 tools (backend=${backendUrl})`);
|
api.logger.info(`[dialectic] registered 7 tools (backend=${backendUrl})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Wraps an async backend call and converts result/error into MCP content shape. */
|
/** Wraps an async backend call and converts result/error into MCP content shape. */
|
||||||
|
|||||||
Reference in New Issue
Block a user