Compare commits
3 Commits
16fdb6600a
...
0a224983fd
| Author | SHA1 | Date | |
|---|---|---|---|
| 0a224983fd | |||
| 1acaebf73f | |||
| b99ec7c281 |
11
CHANGELOG.md
Normal file
11
CHANGELOG.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.0-mvp
|
||||
|
||||
- Added no-reply API service (`/v1/chat/completions`, `/v1/responses`, `/v1/models`)
|
||||
- Added optional bearer auth (`AUTH_TOKEN`)
|
||||
- Added WhisperGate plugin with deterministic rule gate
|
||||
- Added discord-specific 🔚 prompt injection for bypass/end-symbol paths
|
||||
- Added containerization (`Dockerfile`, `docker-compose.yml`)
|
||||
- Added helper scripts for smoke/dev lifecycle and rule validation
|
||||
- Added no-touch config rendering and integration docs
|
||||
8
Makefile
8
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: check check-rules up down smoke
|
||||
.PHONY: check check-rules test-api up down smoke render-config
|
||||
|
||||
check:
|
||||
cd plugin && npm run check
|
||||
@@ -6,6 +6,9 @@ check:
|
||||
check-rules:
|
||||
node scripts/validate-rules.mjs
|
||||
|
||||
test-api:
|
||||
node scripts/test-no-reply-api.mjs
|
||||
|
||||
up:
|
||||
./scripts/dev-up.sh
|
||||
|
||||
@@ -14,3 +17,6 @@ down:
|
||||
|
||||
smoke:
|
||||
./scripts/smoke-no-reply-api.sh
|
||||
|
||||
render-config:
|
||||
node scripts/render-openclaw-config.mjs
|
||||
|
||||
@@ -21,7 +21,8 @@ The no-reply provider returns `NO_REPLY` for any input.
|
||||
- `no-reply-api/` — OpenAI-compatible minimal API that always returns `NO_REPLY`
|
||||
- `docs/` — rollout and configuration notes
|
||||
- `scripts/` — smoke/dev/helper checks
|
||||
- `Makefile` — common dev commands (`make check`, `make check-rules`, `make up`)
|
||||
- `Makefile` — common dev commands (`make check`, `make check-rules`, `make test-api`, `make up`)
|
||||
- `CHANGELOG.md` — milestone summary
|
||||
|
||||
---
|
||||
|
||||
|
||||
34
docs/INTEGRATION.md
Normal file
34
docs/INTEGRATION.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# WhisperGate Integration (No-touch Template)
|
||||
|
||||
This guide **does not** change your current OpenClaw config automatically.
|
||||
It only generates a JSON snippet you can review.
|
||||
|
||||
## Generate config snippet
|
||||
|
||||
```bash
|
||||
node scripts/render-openclaw-config.mjs \
|
||||
/absolute/path/to/WhisperGate/plugin \
|
||||
openai \
|
||||
whispergate-no-reply-v1 \
|
||||
561921120408698910
|
||||
```
|
||||
|
||||
Arguments:
|
||||
1. plugin path
|
||||
2. provider alias
|
||||
3. model name
|
||||
4. bypass user ids (comma-separated, optional)
|
||||
|
||||
## Output
|
||||
|
||||
The script prints JSON for:
|
||||
- `plugins.load.paths`
|
||||
- `plugins.entries.whispergate.config`
|
||||
|
||||
You can merge this snippet manually into your `openclaw.json`.
|
||||
|
||||
## Notes
|
||||
|
||||
- This repo does not run config mutation commands.
|
||||
- Keep no-reply API bound to loopback/private network.
|
||||
- If you use API auth, set `AUTH_TOKEN` and align provider apiKey usage.
|
||||
25
scripts/render-openclaw-config.mjs
Normal file
25
scripts/render-openclaw-config.mjs
Normal file
@@ -0,0 +1,25 @@
|
||||
const pluginPath = process.argv[2] || "/opt/WhisperGate/plugin";
|
||||
const provider = process.argv[3] || "openai";
|
||||
const model = process.argv[4] || "whispergate-no-reply-v1";
|
||||
const bypass = (process.argv[5] || "").split(",").filter(Boolean);
|
||||
|
||||
const payload = {
|
||||
plugins: {
|
||||
load: { paths: [pluginPath] },
|
||||
entries: {
|
||||
whispergate: {
|
||||
enabled: true,
|
||||
config: {
|
||||
enabled: true,
|
||||
discordOnly: true,
|
||||
bypassUserIds: bypass,
|
||||
endSymbols: ["。", "!", "?", ".", "!", "?"],
|
||||
noReplyProvider: provider,
|
||||
noReplyModel: model,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(payload, null, 2));
|
||||
82
scripts/test-no-reply-api.mjs
Normal file
82
scripts/test-no-reply-api.mjs
Normal file
@@ -0,0 +1,82 @@
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
const BASE = "http://127.0.0.1:18787";
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
async function waitForHealth(retries = 30) {
|
||||
for (let i = 0; i < retries; i++) {
|
||||
try {
|
||||
const r = await fetch(`${BASE}/health`);
|
||||
if (r.ok) return true;
|
||||
} catch {}
|
||||
await sleep(200);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function assert(cond, msg) {
|
||||
if (!cond) throw new Error(msg);
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const token = "test-token";
|
||||
const child = spawn("node", ["no-reply-api/server.mjs"], {
|
||||
cwd: process.cwd(),
|
||||
env: { ...process.env, PORT: "18787", AUTH_TOKEN: token, NO_REPLY_MODEL: "wg-test-model" },
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
child.stdout.on("data", () => {});
|
||||
child.stderr.on("data", () => {});
|
||||
|
||||
try {
|
||||
const ok = await waitForHealth();
|
||||
assert(ok, "health check failed");
|
||||
|
||||
const unauth = await fetch(`${BASE}/v1/models`);
|
||||
assert(unauth.status === 401, `expected 401, got ${unauth.status}`);
|
||||
|
||||
const models = await fetch(`${BASE}/v1/models`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
assert(models.ok, "authorized /v1/models failed");
|
||||
const modelsJson = await models.json();
|
||||
assert(modelsJson?.data?.[0]?.id === "wg-test-model", "model id mismatch");
|
||||
|
||||
const cc = await fetch(`${BASE}/v1/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ model: "wg-test-model", messages: [{ role: "user", content: "hi" }] }),
|
||||
});
|
||||
assert(cc.ok, "chat completions failed");
|
||||
const ccJson = await cc.json();
|
||||
assert(ccJson?.choices?.[0]?.message?.content === "NO_REPLY", "chat completion not NO_REPLY");
|
||||
|
||||
const rsp = await fetch(`${BASE}/v1/responses`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ model: "wg-test-model", input: "hi" }),
|
||||
});
|
||||
assert(rsp.ok, "responses failed");
|
||||
const rspJson = await rsp.json();
|
||||
assert(rspJson?.output?.[0]?.content?.[0]?.text === "NO_REPLY", "responses not NO_REPLY");
|
||||
|
||||
console.log("test-no-reply-api: ok");
|
||||
} finally {
|
||||
child.kill("SIGTERM");
|
||||
}
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error(`test-no-reply-api: fail: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user