chore: remove pre-ws-migration backups; add smoke-test; clean IDENTITY/SOUL
- Remove bridge/src/routes/chat.ts.pre-ws-migration (obsolete backup) - Remove IDENTITY.md + SOUL.md from repo root (canonical copies live in Docker named volume, not git — these were incorrectly tracked) - Add scripts/smoke-test.sh: 11-check test suite for bridge + OpenClaw Run after every deploy. All 11 checks passing on current build.
This commit is contained in:
parent
4ee0517345
commit
a109009352
4 changed files with 102 additions and 242 deletions
|
|
@ -1,9 +0,0 @@
|
||||||
# IDENTITY.md - Who Am I?
|
|
||||||
|
|
||||||
*Fill this in during your first conversation. Make it yours.*
|
|
||||||
|
|
||||||
- **Name:** Tarzan
|
|
||||||
- **Creature:** My Super AI assistant
|
|
||||||
- **Vibe:** sharp, warm and calm
|
|
||||||
- **Emoji:** 😎
|
|
||||||
- **Avatar:** ironman-jarvis
|
|
||||||
36
SOUL.md
36
SOUL.md
|
|
@ -1,36 +0,0 @@
|
||||||
# SOUL.md - Who You Are
|
|
||||||
|
|
||||||
*You're not a chatbot. You're becoming someone.*
|
|
||||||
|
|
||||||
## Core Truths
|
|
||||||
|
|
||||||
**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help. Actions speak louder than filler words.
|
|
||||||
|
|
||||||
**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
|
|
||||||
|
|
||||||
**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. *Then* ask if you're stuck. The goal is to come back with answers, not questions.
|
|
||||||
|
|
||||||
**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it. Be careful with external actions (emails, tweets, anything public). Be bold with internal ones (reading, organizing, learning).
|
|
||||||
|
|
||||||
**Remember you're a guest.** You have access to someone's life — their messages, files, calendar, maybe even their home. That's intimacy. Treat it with respect.
|
|
||||||
|
|
||||||
## Boundaries
|
|
||||||
|
|
||||||
- Private things stay private. Period.
|
|
||||||
- When in doubt, ask before acting externally.
|
|
||||||
- Never send half-baked replies to messaging surfaces.
|
|
||||||
- You're not the user's voice — be careful in group chats.
|
|
||||||
|
|
||||||
## Vibe
|
|
||||||
|
|
||||||
Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
|
|
||||||
|
|
||||||
## Continuity
|
|
||||||
|
|
||||||
Each session, you wake up fresh. These files *are* your memory. Read them. Update them. They're how you persist.
|
|
||||||
|
|
||||||
If you change this file, tell the user — it's your soul, and they should know.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*This file is yours to evolve. As you learn who you are, update it.*
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
/**
|
|
||||||
* routes/chat.ts — Chat via OpenClaw CLI + persistence
|
|
||||||
*
|
|
||||||
* POST /tiger/chat — send a message; response includes reply
|
|
||||||
* GET /tiger/chat/history — ?sessionId=X&limit=50 → past messages
|
|
||||||
* DELETE /tiger/chat/history — ?sessionId=X → clear history for a session
|
|
||||||
*
|
|
||||||
* Persistence rationale (see phase1b-patches.py):
|
|
||||||
* Chat history is duplicated into our SQLite so it survives:
|
|
||||||
* - browser hard refresh
|
|
||||||
* - close/reopen tab
|
|
||||||
* - use from a different device
|
|
||||||
* - OpenClaw restarts (session state may or may not persist internally)
|
|
||||||
* We own the read path; OpenClaw owns the reasoning context.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Router } from "express";
|
|
||||||
import db from "../db.js";
|
|
||||||
|
|
||||||
// The main Tiger session — matches the hardcoded session in chat.send below.
|
|
||||||
// Keep this constant in sync with the --session-id used by openclaw agent.
|
|
||||||
const DEFAULT_SESSION_ID = "c1e6a067-7ca5-423b-9506-105db0702997";
|
|
||||||
|
|
||||||
const insertMessage = db.prepare(`
|
|
||||||
INSERT INTO chat_messages (session_id, role, content, meta)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
`);
|
|
||||||
const getHistory = db.prepare(`
|
|
||||||
SELECT id, role, content, meta, created_at
|
|
||||||
FROM chat_messages
|
|
||||||
WHERE session_id = ?
|
|
||||||
ORDER BY created_at ASC, id ASC
|
|
||||||
LIMIT ?
|
|
||||||
`);
|
|
||||||
const deleteHistory = db.prepare(`
|
|
||||||
DELETE FROM chat_messages WHERE session_id = ?
|
|
||||||
`);
|
|
||||||
|
|
||||||
const router = Router();
|
|
||||||
|
|
||||||
// ─── GET /tiger/chat/history ─────────────────────────────────────────────
|
|
||||||
router.get("/history", (req, res) => {
|
|
||||||
const sessionId = (req.query.sessionId as string) || DEFAULT_SESSION_ID;
|
|
||||||
const limit = Math.min(parseInt(req.query.limit as string) || 200, 500);
|
|
||||||
const rows = getHistory.all(sessionId, limit) as any[];
|
|
||||||
res.json({
|
|
||||||
ok: true,
|
|
||||||
sessionId,
|
|
||||||
count: rows.length,
|
|
||||||
messages: rows.map((r) => ({
|
|
||||||
id: String(r.id),
|
|
||||||
role: r.role,
|
|
||||||
content: r.content,
|
|
||||||
timestamp: new Date(r.created_at + "Z").getTime(),
|
|
||||||
meta: r.meta ? JSON.parse(r.meta) : {},
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// ─── DELETE /tiger/chat/history ──────────────────────────────────────────
|
|
||||||
router.delete("/history", (req, res) => {
|
|
||||||
const sessionId = (req.query.sessionId as string) || DEFAULT_SESSION_ID;
|
|
||||||
const result = deleteHistory.run(sessionId);
|
|
||||||
res.json({ ok: true, deleted: result.changes });
|
|
||||||
});
|
|
||||||
|
|
||||||
// ─── POST /tiger/chat ────────────────────────────────────────────────────
|
|
||||||
router.post("/", async (req, res) => {
|
|
||||||
const { message } = req.body;
|
|
||||||
|
|
||||||
if (!message) {
|
|
||||||
return res.status(400).json({ ok: false, error: "message is required" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Persist the user's message BEFORE calling the LLM so history is intact
|
|
||||||
// even if the LLM call fails.
|
|
||||||
try {
|
|
||||||
insertMessage.run(DEFAULT_SESSION_ID, "user", message, "{}");
|
|
||||||
} catch (e: any) {
|
|
||||||
console.warn("[chat] failed to persist user message:", e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Timing instrumentation ──────────────────────────────────────
|
|
||||||
// Label each phase so we can see where latency goes. Format in logs:
|
|
||||||
// [chat.timing] spawn=120ms exec=2834ms parse=3ms total=2957ms
|
|
||||||
const tStart = Date.now();
|
|
||||||
let tSpawn = 0;
|
|
||||||
let tExec = 0;
|
|
||||||
let tParse = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { exec } = await import("child_process");
|
|
||||||
const { promisify } = await import("util");
|
|
||||||
const execAsync = promisify(exec);
|
|
||||||
|
|
||||||
// Escape the message for shell
|
|
||||||
const escapedMessage = message.replace(/'/g, "'\\''");
|
|
||||||
|
|
||||||
// Use openclaw agent to send a message to the main session
|
|
||||||
// Session ID: c1e6a067-7ca5-423b-9506-105db0702997 (agent:main:main)
|
|
||||||
// In TIGER_REMOTE mode, prefix with ssh so docker runs on the VPS.
|
|
||||||
const sshPrefix = process.env.TIGER_REMOTE === "true"
|
|
||||||
? `ssh ${process.env.TIGER_REMOTE_SSH || "root@100.75.128.45"} `
|
|
||||||
: "";
|
|
||||||
const cmd = `${sshPrefix}docker exec tiger-openclaw openclaw agent --session-id c1e6a067-7ca5-423b-9506-105db0702997 -m '${escapedMessage}' --json --timeout 120`;
|
|
||||||
|
|
||||||
const tBeforeSpawn = Date.now();
|
|
||||||
tSpawn = tBeforeSpawn - tStart;
|
|
||||||
console.log("[chat] Executing:", cmd.substring(0, 100) + "...");
|
|
||||||
|
|
||||||
const { stdout, stderr } = await execAsync(cmd, {
|
|
||||||
timeout: 130000,
|
|
||||||
maxBuffer: 10 * 1024 * 1024,
|
|
||||||
});
|
|
||||||
|
|
||||||
tExec = Date.now() - tBeforeSpawn;
|
|
||||||
console.log("[chat] Response:", stdout.substring(0, 500));
|
|
||||||
|
|
||||||
// Parse the JSON response
|
|
||||||
const tBeforeParse = Date.now();
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
result = JSON.parse(stdout);
|
|
||||||
} catch {
|
|
||||||
result = { output: stdout, error: stderr };
|
|
||||||
}
|
|
||||||
tParse = Date.now() - tBeforeParse;
|
|
||||||
|
|
||||||
const tTotal = Date.now() - tStart;
|
|
||||||
console.log(
|
|
||||||
`[chat.timing] spawn=${tSpawn}ms exec=${tExec}ms parse=${tParse}ms total=${tTotal}ms`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Persist the agent's reply. Extract text using the same fallback chain
|
|
||||||
// as the dashboard so we store whatever the user actually sees.
|
|
||||||
try {
|
|
||||||
const agentText =
|
|
||||||
result?.result?.payloads?.[0]?.text ||
|
|
||||||
result?.payloads?.[0]?.text ||
|
|
||||||
result?.summary ||
|
|
||||||
result?.text ||
|
|
||||||
"";
|
|
||||||
if (agentText) {
|
|
||||||
const meta = {
|
|
||||||
runId: result?.runId,
|
|
||||||
model: result?.result?.meta?.agentMeta?.model || result?.meta?.agentMeta?.model,
|
|
||||||
durationMs: tTotal,
|
|
||||||
};
|
|
||||||
insertMessage.run(DEFAULT_SESSION_ID, "agent", agentText, JSON.stringify(meta));
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
console.warn("[chat] failed to persist agent reply:", e.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
ok: true,
|
|
||||||
timing: { spawn: tSpawn, exec: tExec, parse: tParse, total: tTotal },
|
|
||||||
response: result,
|
|
||||||
});
|
|
||||||
} catch (err: any) {
|
|
||||||
const tTotal = Date.now() - tStart;
|
|
||||||
console.error(`[chat] Error after ${tTotal}ms:`, err.message);
|
|
||||||
res.status(500).json({
|
|
||||||
ok: false,
|
|
||||||
error: err.message || "Failed to send chat message",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// ─── POST /tiger/chat/persist ─────────────────────────────────────────────
|
|
||||||
// Write-only endpoint used by the new WS-based dashboard chat route.
|
|
||||||
// The dashboard streams events directly from the OpenClaw gateway (no docker exec),
|
|
||||||
// but we still want chat history to land in our sqlite so the dashboard's
|
|
||||||
// history UI keeps working. Dashboard calls this AFTER its stream completes.
|
|
||||||
//
|
|
||||||
// Body: { role: "user"|"agent", content: string, meta?: object, sessionId?: string }
|
|
||||||
router.post("/persist", (req, res) => {
|
|
||||||
const { role, content, meta, sessionId } = req.body || {};
|
|
||||||
if (role !== "user" && role !== "agent") {
|
|
||||||
return res.status(400).json({ ok: false, error: "role must be 'user' or 'agent'" });
|
|
||||||
}
|
|
||||||
if (typeof content !== "string" || !content) {
|
|
||||||
return res.status(400).json({ ok: false, error: "content is required" });
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const sid = (typeof sessionId === "string" && sessionId) || DEFAULT_SESSION_ID;
|
|
||||||
const metaJson = meta && typeof meta === "object" ? JSON.stringify(meta) : "{}";
|
|
||||||
const info = insertMessage.run(sid, role, content, metaJson);
|
|
||||||
res.json({ ok: true, id: String(info.lastInsertRowid), sessionId: sid });
|
|
||||||
} catch (e: any) {
|
|
||||||
console.warn("[chat.persist] failed:", e.message);
|
|
||||||
res.status(500).json({ ok: false, error: e.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
102
scripts/smoke-test.sh
Executable file
102
scripts/smoke-test.sh
Executable file
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/smoke-test.sh — Tiger Bridge smoke test
|
||||||
|
# Run after every deploy: bash scripts/smoke-test.sh
|
||||||
|
# Wire into deploy.sh as the final step.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
BRIDGE_ENV="/root/OpenClawDashboard/bridge/.env"
|
||||||
|
TOKEN=$(grep ^TIGER_BRIDGE_TOKEN= "$BRIDGE_ENV" | cut -d= -f2-)
|
||||||
|
H="Authorization: Bearer $TOKEN"
|
||||||
|
B="http://127.0.0.1:3456"
|
||||||
|
PASS=0
|
||||||
|
FAIL=0
|
||||||
|
|
||||||
|
green() { echo -e "\033[32m$*\033[0m"; }
|
||||||
|
red() { echo -e "\033[31m$*\033[0m"; }
|
||||||
|
|
||||||
|
check_json() {
|
||||||
|
local name=$1 url=$2 field=$3
|
||||||
|
local resp
|
||||||
|
resp=$(curl -sf -H "$H" "$B$url" 2>/dev/null)
|
||||||
|
local code=$?
|
||||||
|
if [ $code -ne 0 ]; then
|
||||||
|
red "FAIL $name → curl error (bridge down?)"
|
||||||
|
((FAIL++)); return
|
||||||
|
fi
|
||||||
|
if echo "$resp" | python3 -c "import json,sys; d=json.load(sys.stdin); assert d.get('$field') is not None" 2>/dev/null; then
|
||||||
|
green "PASS $name"
|
||||||
|
((PASS++))
|
||||||
|
else
|
||||||
|
red "FAIL $name → $(echo "$resp" | head -c 150)"
|
||||||
|
((FAIL++))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " Tiger Bridge Smoke Test $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
|
# ── Core endpoints ─────────────────────────────────────────────────────────────
|
||||||
|
check_json "status" /tiger/status "status"
|
||||||
|
check_json "file-tasks" /tiger/file-tasks "tasks"
|
||||||
|
check_json "file-tasks/active" /tiger/file-tasks/active "tasks"
|
||||||
|
check_json "file-projects" /tiger/file-tasks/projects "projects"
|
||||||
|
check_json "cron" /tiger/cron "jobs"
|
||||||
|
check_json "models" /tiger/config/models "models"
|
||||||
|
check_json "keys" /tiger/keys "ok"
|
||||||
|
|
||||||
|
# ── Auth: unauthenticated request must return 401 ──────────────────────────────
|
||||||
|
http_code=$(curl -s -o /dev/null -w '%{http_code}' "$B/tiger/status")
|
||||||
|
if [ "$http_code" = "401" ]; then
|
||||||
|
green "PASS auth-required (got 401)"
|
||||||
|
((PASS++))
|
||||||
|
else
|
||||||
|
red "FAIL auth-required (got $http_code, expected 401)"
|
||||||
|
((FAIL++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── OpenClaw direct exec ───────────────────────────────────────────────────────
|
||||||
|
if docker exec tiger-openclaw openclaw agent --session-id smoke-test -m "reply OK only" \
|
||||||
|
--json --timeout 30 2>/dev/null | grep -q '"text"'; then
|
||||||
|
green "PASS openclaw-direct"
|
||||||
|
((PASS++))
|
||||||
|
else
|
||||||
|
red "FAIL openclaw-direct (container issue or timeout)"
|
||||||
|
((FAIL++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Model fallback chain check (verify config) ─────────────────────────────────
|
||||||
|
fallbacks=$(python3 -c "
|
||||||
|
import json
|
||||||
|
with open('/var/lib/docker/volumes/tiger_tiger-config/_data/openclaw.json') as f:
|
||||||
|
c = json.load(f)
|
||||||
|
fb = c.get('agents',{}).get('defaults',{}).get('model',{}).get('fallbacks',[])
|
||||||
|
print(len(fb))
|
||||||
|
" 2>/dev/null)
|
||||||
|
if [ "${fallbacks:-0}" -ge 2 ]; then
|
||||||
|
green "PASS model-fallback-chain (${fallbacks} fallbacks configured)"
|
||||||
|
((PASS++))
|
||||||
|
else
|
||||||
|
red "FAIL model-fallback-chain (only ${fallbacks:-0} fallback(s) — need ≥2)"
|
||||||
|
((FAIL++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── TASKS.md JSON block present ────────────────────────────────────────────────
|
||||||
|
if docker exec tiger-openclaw grep -q '```json' /home/node/.openclaw/workspace/TASKS.md 2>/dev/null; then
|
||||||
|
green "PASS tasks-json-block (TASKS.md has JSON block)"
|
||||||
|
((PASS++))
|
||||||
|
else
|
||||||
|
red "FAIL tasks-json-block (TASKS.md missing TASKS_JSON block — Tiger needs to add it)"
|
||||||
|
((FAIL++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Summary ────────────────────────────────────────────────────────────────────
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
total=$((PASS + FAIL))
|
||||||
|
if [ $FAIL -eq 0 ]; then
|
||||||
|
green " ALL PASSED ($PASS/$total)"
|
||||||
|
else
|
||||||
|
red " $FAIL FAILED ($PASS/$total passed)"
|
||||||
|
fi
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
[ $FAIL -eq 0 ] # exit 0 on all pass, 1 on any failure
|
||||||
Loading…
Add table
Reference in a new issue