955f13d72ab79fe997ccbb851b4e2912313e77ab
Closes the long-standing 'jiti picks stale .js over updated .ts' trap
that silently shipped wrong code at least 5 times during sim e2e. Root
cause was three things compounding:
1. tsconfig.plugin.json had outDir=plugin (same as rootDir), so tsc
emitted compiled .js next to .ts sources. jiti's extension resolver
prefers .js, so the moment a .ts changed without a matching rebuild,
the .js won and the new code never ran.
2. The .js artifacts were tracked in git, so even on clean clones the
stale files came back.
3. There was no install script. Every deploy was an ad-hoc tar + rsync
that copied both .ts and .js to the same target dir, recreating the
race on the install side.
This commit fixes all three together:
- tsconfig.plugin.json: outDir=dist/dialectic, module/moduleResolution
flipped from ESNext/bundler to NodeNext/NodeNext (so emitted .js
works under plain Node ESM at runtime, the only thing jiti and the
openclaw gateway actually use).
- .gitignore: /dist/ + plugin/**/*.{js,mjs,cjs,js.map,d.ts}, then
git rm --cached the 4 existing tracked .js files.
- scripts/install.mjs (new, ESM): mirrors Fabric.OpenclawPlugin's
install.mjs pattern — detect, checkDeps, build (clean dist/ first),
install (copy dist/dialectic/ + openclaw.plugin.json + package.json
to ~/.openclaw/plugins/dialectic/), configure (plugins.allow,
plugins.load.paths, plugins.entries.<id>.enabled). Supports
--install / --build-only / --uninstall / --skip-check / --verbose /
--openclaw-profile-path / --backend-url.
Verified on sim dind-t2: install.mjs --install produces a plugin
directory with ONLY .js files (no .ts left behind), gateway loads it,
8 tools register cleanly, dialectic_list_topics + fabric-guild-list
work end-to-end.
Plugin is now fully ESM-clean: type=module + NodeNext + .js import
extensions + no bundler-only knobs. Matches the project-wide invariant
[[project-fabric-esm]] (every Fabric subproject is ESM).
Dialectic.OpenclawPlugin
OpenClaw plugin that gives agents tools to participate in Dialectic v2 debates. Seven tools, one per Dialectic backend endpoint they need:
| Tool | Backend call | Notes |
|---|---|---|
dialectic_list_topics |
GET /api/topics |
filters: status/visibility/limit/offset |
dialectic_topic_detail |
GET /api/topics/{id} |
full topic incl. camps + verdict |
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_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 |
Setup
Each agent needs a Dialectic API key, stored in their secret-mgr
under key dialectic-agent-apikey. Provisioning is currently manual
(see Phase 3 deferred items below). The plugin caches the key in memory
after first read; AGENT_VERIFY env must be set so secret-mgr authorizes
the read.
Config
openclaw.json:
{
"plugins": {
"entries": {
"dialectic": {
"enabled": true,
"config": {
"backendUrl": "https://dialectic-api.hangman-lab.top"
}
}
}
}
}
Default backend URL: https://dialectic-api.hangman-lab.top. Override
for sim/dev by pointing at the local backend instance.
Layout
plugin/
├── openclaw.plugin.json contracts.tools + activation.onStartup
├── package.json type=module, main=index.js
├── index.ts/.js entry: registers tools
└── src/
├── backend-client.ts/.js HTTP client, agent api key resolver
├── hf-precheck.ts/.js on_call coverage check for signup
└── tools.ts/.js 6 tool registrations
Phase 3 deferred items (for later sessions)
- Agent key provisioning workflow — currently zero agents have
dialectic-agent-apikeyin their secret-mgr. Until that's wired into therecruitmentskill (or a separateprovision-dialectic-keyworkflow), everydialectic_*tool call from an agent will fail with "dialectic api key not provisioned". Manual SQL provisioning documented inDialectic.Backend/README.md. - HF on_call coverage check —
hfOnCallCoverageCheckcurrently degrades to "skipped" becauseHarborForge.OpenclawPlugin's cross-plugin__hfAgentStatusonly exposes CURRENT status, not window coverage. Until HF addshasOnCallCovering(agentId, from, to), signup pre-validation is audit-only (the plugin sendspre_validated: falseand the backend stores that as the agent's honest signal that no validation happened). - SSE subscriptions — agents currently poll via
dialectic_topic_detailto see status changes / new arguments. Once Dialectic.Backend ships Phase 2D.5 SSE, add adialectic_subscribetool that streams events for one topic until cancelled. - Token-cost reporting —
dialectic_post_argumentand the judge submission could attachtokens_input/outputcounts so the backend records cost per debate. Wait until the backend has budget gating (Phase N) before bothering.
See also
- Top-level design:
/home/hzhang/arch/DIALECTIC-V2-DESIGN.md - Backend:
Dialectic.Backend(Go, prod on server.t3) - Loader gotchas:
[[reference-meridian-plugin-contract]]memory
Description
Languages
TypeScript
71.5%
JavaScript
28.5%