Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
382
extensions/feishu/src/doctor.test.ts
Normal file
382
extensions/feishu/src/doctor.test.ts
Normal file
@@ -0,0 +1,382 @@
|
||||
// Feishu tests cover doctor plugin behavior.
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { loadSessionStore } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../runtime-api.js";
|
||||
import { isFeishuSessionStoreKey, runFeishuDoctorSequence } from "./doctor.js";
|
||||
|
||||
type EnvSnapshot = {
|
||||
HOME?: string;
|
||||
OPENCLAW_HOME?: string;
|
||||
OPENCLAW_STATE_DIR?: string;
|
||||
};
|
||||
|
||||
function captureEnv(): EnvSnapshot {
|
||||
return {
|
||||
HOME: process.env.HOME,
|
||||
OPENCLAW_HOME: process.env.OPENCLAW_HOME,
|
||||
OPENCLAW_STATE_DIR: process.env.OPENCLAW_STATE_DIR,
|
||||
};
|
||||
}
|
||||
|
||||
function restoreEnv(snapshot: EnvSnapshot) {
|
||||
for (const key of Object.keys(snapshot) as Array<keyof EnvSnapshot>) {
|
||||
const value = snapshot[key];
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function feishuConfig(): OpenClawConfig {
|
||||
return {
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "cli_xxx",
|
||||
appSecret: "secret_xxx",
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
}
|
||||
|
||||
function stateDir(): string {
|
||||
const dir = process.env.OPENCLAW_STATE_DIR;
|
||||
if (!dir) {
|
||||
throw new Error("OPENCLAW_STATE_DIR is not set");
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
function sessionsDir(agentId = "main"): string {
|
||||
return path.join(stateDir(), "agents", agentId, "sessions");
|
||||
}
|
||||
|
||||
function storePath(agentId = "main"): string {
|
||||
return path.join(sessionsDir(agentId), "sessions.json");
|
||||
}
|
||||
|
||||
function writeStore(entries: Record<string, unknown>, agentId = "main"): string {
|
||||
const target = storePath(agentId);
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
fs.writeFileSync(target, JSON.stringify(entries, null, 2));
|
||||
return target;
|
||||
}
|
||||
|
||||
function writeTranscript(sessionId: string, lines: unknown[], agentId = "main"): string {
|
||||
const target = path.join(sessionsDir(agentId), `${sessionId}.jsonl`);
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
fs.writeFileSync(target, `${lines.map((line) => JSON.stringify(line)).join("\n")}\n`);
|
||||
return target;
|
||||
}
|
||||
|
||||
function sessionHeader(sessionId: string) {
|
||||
return {
|
||||
type: "session",
|
||||
id: sessionId,
|
||||
version: 7,
|
||||
timestamp: new Date(0).toISOString(),
|
||||
cwd: "/tmp",
|
||||
};
|
||||
}
|
||||
|
||||
function userMessage(content: string) {
|
||||
return {
|
||||
type: "message",
|
||||
id: `msg-${content || "blank"}-${Math.random().toString(36).slice(2)}`,
|
||||
parentId: null,
|
||||
timestamp: new Date(0).toISOString(),
|
||||
message: { role: "user", content },
|
||||
};
|
||||
}
|
||||
|
||||
function listBackupDirs(): string[] {
|
||||
const backupsDir = path.join(stateDir(), "backups");
|
||||
return fs.existsSync(backupsDir)
|
||||
? fs.readdirSync(backupsDir).filter((name) => name.startsWith("feishu-state-repair-"))
|
||||
: [];
|
||||
}
|
||||
|
||||
describe("Feishu doctor state repair", () => {
|
||||
let envSnapshot: EnvSnapshot;
|
||||
let tempHome = "";
|
||||
|
||||
beforeEach(() => {
|
||||
envSnapshot = captureEnv();
|
||||
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-feishu-doctor-"));
|
||||
process.env.HOME = tempHome;
|
||||
process.env.OPENCLAW_HOME = tempHome;
|
||||
process.env.OPENCLAW_STATE_DIR = path.join(tempHome, ".openclaw");
|
||||
fs.mkdirSync(process.env.OPENCLAW_STATE_DIR, { recursive: true, mode: 0o700 });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
restoreEnv(envSnapshot);
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("matches only Feishu channel session keys", () => {
|
||||
expect(isFeishuSessionStoreKey("agent:main:feishu:direct:ou_user")).toBe(true);
|
||||
expect(isFeishuSessionStoreKey("feishu:direct:ou_user")).toBe(true);
|
||||
expect(isFeishuSessionStoreKey("agent:codex:acp:binding:feishu:default:abc123")).toBe(false);
|
||||
expect(isFeishuSessionStoreKey("agent:main:discord:direct:user")).toBe(false);
|
||||
});
|
||||
|
||||
it("stays quiet for healthy Feishu state and transcripts", async () => {
|
||||
const feishuDedupDir = path.join(stateDir(), "feishu", "dedup");
|
||||
fs.mkdirSync(feishuDedupDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(feishuDedupDir, "default.json"), JSON.stringify({ msg1: 1 }));
|
||||
|
||||
writeTranscript("sess-ok", [sessionHeader("sess-ok"), userMessage("hello")]);
|
||||
writeStore({
|
||||
"agent:main:feishu:direct:ou_user": {
|
||||
sessionId: "sess-ok",
|
||||
sessionFile: "sess-ok.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: feishuConfig(),
|
||||
env: process.env,
|
||||
shouldRepair: false,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ changeNotes: [], warningNotes: [] });
|
||||
});
|
||||
|
||||
it("keeps custom-store sessions with canonical absolute transcripts", async () => {
|
||||
const transcriptPath = writeTranscript("sess-abs", [
|
||||
sessionHeader("sess-abs"),
|
||||
userMessage("hello"),
|
||||
]);
|
||||
const customStorePath = path.join(stateDir(), "custom-sessions", "sessions.json");
|
||||
fs.mkdirSync(path.dirname(customStorePath), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
customStorePath,
|
||||
JSON.stringify({
|
||||
"agent:main:feishu:direct:ou_user": {
|
||||
sessionId: "sess-abs",
|
||||
sessionFile: transcriptPath,
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: {
|
||||
...feishuConfig(),
|
||||
session: { store: customStorePath },
|
||||
} as OpenClawConfig,
|
||||
env: process.env,
|
||||
shouldRepair: false,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ changeNotes: [], warningNotes: [] });
|
||||
});
|
||||
|
||||
it("keeps Feishu sessions with separated blank user messages", async () => {
|
||||
writeTranscript("sess-separated-blanks", [
|
||||
sessionHeader("sess-separated-blanks"),
|
||||
userMessage(""),
|
||||
userMessage("hello"),
|
||||
userMessage(""),
|
||||
userMessage("world"),
|
||||
userMessage(""),
|
||||
]);
|
||||
writeStore({
|
||||
"agent:main:feishu:direct:ou_user": {
|
||||
sessionId: "sess-separated-blanks",
|
||||
sessionFile: "sess-separated-blanks.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: feishuConfig(),
|
||||
env: process.env,
|
||||
shouldRepair: false,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ changeNotes: [], warningNotes: [] });
|
||||
});
|
||||
|
||||
it("warns before repair when Feishu local state is corrupt", async () => {
|
||||
const feishuDedupDir = path.join(stateDir(), "feishu", "dedup");
|
||||
fs.mkdirSync(feishuDedupDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(feishuDedupDir, "default.json"), "{");
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: feishuConfig(),
|
||||
env: process.env,
|
||||
shouldRepair: false,
|
||||
});
|
||||
|
||||
expect(result.changeNotes).toEqual([]);
|
||||
expect(result.warningNotes.join("\n")).toContain("Feishu local channel state may need repair");
|
||||
expect(result.warningNotes.join("\n")).toContain("preserving Feishu App ID/secret config");
|
||||
expect(result.warningNotes.join("\n")).toContain("openclaw doctor --fix");
|
||||
});
|
||||
|
||||
it("rebuilds corrupt Feishu state without deleting healthy Feishu sessions", async () => {
|
||||
const feishuDedupDir = path.join(stateDir(), "feishu", "dedup");
|
||||
fs.mkdirSync(feishuDedupDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(feishuDedupDir, "default.json"), "{");
|
||||
|
||||
const transcriptPath = writeTranscript("sess-ok", [
|
||||
sessionHeader("sess-ok"),
|
||||
userMessage("hello"),
|
||||
]);
|
||||
const targetStorePath = writeStore({
|
||||
"agent:main:feishu:direct:ou_user": {
|
||||
sessionId: "sess-ok",
|
||||
sessionFile: "sess-ok.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: feishuConfig(),
|
||||
env: process.env,
|
||||
shouldRepair: true,
|
||||
});
|
||||
|
||||
expect(result.warningNotes).toEqual([]);
|
||||
expect(result.changeNotes.join("\n")).toContain("Rebuilt Feishu runtime state: yes");
|
||||
expect(result.changeNotes.join("\n")).toContain("Removed 0 Feishu-scoped session entries");
|
||||
|
||||
const store = loadSessionStore(targetStorePath, { skipCache: true });
|
||||
expect(store["agent:main:feishu:direct:ou_user"]).toBeDefined();
|
||||
expect(fs.existsSync(transcriptPath)).toBe(true);
|
||||
|
||||
expect(fs.existsSync(path.join(stateDir(), "feishu"))).toBe(true);
|
||||
expect(fs.existsSync(path.join(stateDir(), "feishu", "dedup", "default.json"))).toBe(false);
|
||||
|
||||
const backups = listBackupDirs();
|
||||
expect(backups).toHaveLength(1);
|
||||
const backupDir = path.join(stateDir(), "backups", backups[0] ?? "");
|
||||
expect(fs.existsSync(path.join(backupDir, "feishu", "dedup", "default.json"))).toBe(true);
|
||||
expect(fs.existsSync(path.join(backupDir, "session-stores", "main", "sessions.json"))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("archives only unhealthy Feishu direct sessions while preserving state, config, and other sessions", async () => {
|
||||
const feishuDedupDir = path.join(stateDir(), "feishu", "dedup");
|
||||
fs.mkdirSync(feishuDedupDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(feishuDedupDir, "default.json"), JSON.stringify({ msg1: 1 }));
|
||||
|
||||
const transcriptPath = writeTranscript("sess-bad", [
|
||||
sessionHeader("sess-bad"),
|
||||
userMessage(""),
|
||||
userMessage(""),
|
||||
userMessage(""),
|
||||
]);
|
||||
const trajectoryPath = path.join(sessionsDir(), "sess-bad.trajectory.jsonl");
|
||||
const trajectoryIndexPath = path.join(sessionsDir(), "sess-bad.trajectory-path.json");
|
||||
fs.writeFileSync(trajectoryPath, "{}\n");
|
||||
fs.writeFileSync(trajectoryIndexPath, "{}\n");
|
||||
const acpTranscriptPath = writeTranscript("sess-acp-bad", [
|
||||
sessionHeader("sess-acp-bad"),
|
||||
userMessage(""),
|
||||
userMessage(""),
|
||||
userMessage(""),
|
||||
]);
|
||||
|
||||
const targetStorePath = writeStore({
|
||||
"agent:main:feishu:direct:ou_user": {
|
||||
sessionId: "sess-bad",
|
||||
sessionFile: "sess-bad.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
"agent:codex:acp:binding:feishu:default:abc123": {
|
||||
sessionId: "sess-acp-bad",
|
||||
sessionFile: "sess-acp-bad.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
route: { channel: "feishu", target: { to: "ou_user", chatType: "direct" } },
|
||||
},
|
||||
"agent:main:discord:direct:user": {
|
||||
sessionId: "sess-discord",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: feishuConfig(),
|
||||
env: process.env,
|
||||
shouldRepair: true,
|
||||
});
|
||||
|
||||
expect(result.warningNotes).toEqual([]);
|
||||
expect(result.changeNotes.join("\n")).toContain("Feishu local state repaired");
|
||||
expect(result.changeNotes.join("\n")).toContain("Rebuilt Feishu runtime state: not needed");
|
||||
expect(result.changeNotes.join("\n")).toContain("Preserved Feishu App ID/secret config");
|
||||
|
||||
expect(fs.existsSync(path.join(stateDir(), "feishu"))).toBe(true);
|
||||
expect(fs.existsSync(path.join(stateDir(), "feishu", "dedup", "default.json"))).toBe(true);
|
||||
|
||||
const backups = listBackupDirs();
|
||||
expect(backups).toHaveLength(1);
|
||||
const backupDir = path.join(stateDir(), "backups", backups[0] ?? "");
|
||||
expect(fs.existsSync(path.join(backupDir, "feishu", "dedup", "default.json"))).toBe(false);
|
||||
expect(fs.existsSync(path.join(backupDir, "session-stores", "main", "sessions.json"))).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
const store = loadSessionStore(targetStorePath, { skipCache: true });
|
||||
expect(store["agent:main:feishu:direct:ou_user"]).toBeUndefined();
|
||||
expect(store["agent:codex:acp:binding:feishu:default:abc123"]).toBeDefined();
|
||||
expect(store["agent:main:discord:direct:user"]).toBeDefined();
|
||||
|
||||
expect(fs.existsSync(transcriptPath)).toBe(false);
|
||||
expect(fs.existsSync(acpTranscriptPath)).toBe(true);
|
||||
expect(fs.existsSync(trajectoryPath)).toBe(false);
|
||||
expect(fs.existsSync(trajectoryIndexPath)).toBe(false);
|
||||
const archivedNames = fs.readdirSync(sessionsDir());
|
||||
expect(archivedNames.some((name) => name.startsWith("sess-bad.jsonl.deleted."))).toBe(true);
|
||||
expect(
|
||||
archivedNames.some((name) => name.startsWith("sess-bad.trajectory.jsonl.deleted.")),
|
||||
).toBe(true);
|
||||
expect(
|
||||
archivedNames.some((name) => name.startsWith("sess-bad.trajectory-path.json.deleted.")),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("archives unhealthy default-scope sessions when metadata identifies Feishu", async () => {
|
||||
const transcriptPath = writeTranscript("sess-default-feishu-bad", [
|
||||
sessionHeader("sess-default-feishu-bad"),
|
||||
userMessage(""),
|
||||
userMessage(""),
|
||||
userMessage(""),
|
||||
]);
|
||||
const targetStorePath = writeStore({
|
||||
"agent:main:main": {
|
||||
sessionId: "sess-default-feishu-bad",
|
||||
sessionFile: "sess-default-feishu-bad.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
origin: { provider: "feishu", from: "feishu:ou_user" },
|
||||
route: { channel: "feishu", target: { to: "ou_user", chatType: "direct" } },
|
||||
},
|
||||
"agent:main:main-non-feishu": {
|
||||
sessionId: "sess-other",
|
||||
updatedAt: Date.now(),
|
||||
origin: { provider: "discord" },
|
||||
},
|
||||
});
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: feishuConfig(),
|
||||
env: process.env,
|
||||
shouldRepair: true,
|
||||
});
|
||||
|
||||
expect(result.warningNotes).toEqual([]);
|
||||
const store = loadSessionStore(targetStorePath, { skipCache: true });
|
||||
expect(store["agent:main:main"]).toBeUndefined();
|
||||
expect(store["agent:main:main-non-feishu"]).toBeDefined();
|
||||
expect(fs.existsSync(transcriptPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user