mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-16 10:23:17 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
184
openclaw/extensions/tlon/index.ts
Normal file
184
openclaw/extensions/tlon/index.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import { spawn } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const ALLOWED_TLON_COMMANDS = new Set([
|
||||
"activity",
|
||||
"channels",
|
||||
"contacts",
|
||||
"groups",
|
||||
"messages",
|
||||
"dms",
|
||||
"posts",
|
||||
"notebook",
|
||||
"settings",
|
||||
"help",
|
||||
"version",
|
||||
]);
|
||||
|
||||
let cachedTlonBinary: string | undefined;
|
||||
|
||||
function findTlonBinary(): string {
|
||||
if (cachedTlonBinary) {
|
||||
return cachedTlonBinary;
|
||||
}
|
||||
const skillBin = join(__dirname, "node_modules", ".bin", "tlon");
|
||||
if (existsSync(skillBin)) {
|
||||
cachedTlonBinary = skillBin;
|
||||
return skillBin;
|
||||
}
|
||||
|
||||
const platformPkg = `@tloncorp/tlon-skill-${process.platform}-${process.arch}`;
|
||||
const platformBin = join(__dirname, "node_modules", platformPkg, "tlon");
|
||||
if (existsSync(platformBin)) {
|
||||
cachedTlonBinary = platformBin;
|
||||
return platformBin;
|
||||
}
|
||||
|
||||
cachedTlonBinary = "tlon";
|
||||
return cachedTlonBinary;
|
||||
}
|
||||
|
||||
function shellSplit(str: string): string[] {
|
||||
const args: string[] = [];
|
||||
let cur = "";
|
||||
let inDouble = false;
|
||||
let inSingle = false;
|
||||
let escape = false;
|
||||
|
||||
for (const ch of str) {
|
||||
if (escape) {
|
||||
cur += ch;
|
||||
escape = false;
|
||||
continue;
|
||||
}
|
||||
if (ch === "\\" && !inSingle) {
|
||||
escape = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === '"' && !inSingle) {
|
||||
inDouble = !inDouble;
|
||||
continue;
|
||||
}
|
||||
if (ch === "'" && !inDouble) {
|
||||
inSingle = !inSingle;
|
||||
continue;
|
||||
}
|
||||
if (/\s/.test(ch) && !inDouble && !inSingle) {
|
||||
if (cur) {
|
||||
args.push(cur);
|
||||
cur = "";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cur += ch;
|
||||
}
|
||||
if (cur) {
|
||||
args.push(cur);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function runTlonCommand(binary: string, args: string[]): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(binary, args, { env: process.env });
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
child.stdout.on("data", (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.on("error", (err) => {
|
||||
reject(new Error(`Failed to run tlon: ${err.message}`));
|
||||
});
|
||||
|
||||
child.on("close", (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(stderr || `tlon exited with code ${code}`));
|
||||
return;
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default defineBundledChannelEntry({
|
||||
id: "tlon",
|
||||
name: "Tlon",
|
||||
description: "Tlon/Urbit channel plugin",
|
||||
importMetaUrl: import.meta.url,
|
||||
plugin: {
|
||||
specifier: "./api.js",
|
||||
exportName: "tlonPlugin",
|
||||
},
|
||||
runtime: {
|
||||
specifier: "./api.js",
|
||||
exportName: "setTlonRuntime",
|
||||
},
|
||||
registerFull(api) {
|
||||
api.logger.debug?.("[tlon] Registering tlon tool");
|
||||
api.registerTool({
|
||||
name: "tlon",
|
||||
label: "Tlon CLI",
|
||||
description:
|
||||
"Tlon/Urbit API operations: activity, channels, contacts, groups, messages, dms, posts, notebook, settings. " +
|
||||
"Examples: 'activity mentions --limit 10', 'channels groups', 'contacts self', 'groups list'",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
command: {
|
||||
type: "string",
|
||||
description:
|
||||
"The tlon command and arguments. " +
|
||||
"Examples: 'activity mentions --limit 10', 'contacts get ~sampel-palnet', 'groups list'",
|
||||
},
|
||||
},
|
||||
required: ["command"],
|
||||
},
|
||||
async execute(_id: string, params: { command: string }) {
|
||||
try {
|
||||
const args = shellSplit(params.command);
|
||||
const subcommand = args[0];
|
||||
if (!ALLOWED_TLON_COMMANDS.has(subcommand)) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `Error: Unknown tlon subcommand '${subcommand}'. Allowed: ${[...ALLOWED_TLON_COMMANDS].join(", ")}`,
|
||||
},
|
||||
],
|
||||
details: { error: true },
|
||||
};
|
||||
}
|
||||
|
||||
const output = await runTlonCommand(findTlonBinary(), args);
|
||||
return {
|
||||
content: [{ type: "text" as const, text: output }],
|
||||
details: undefined,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: `Error: ${formatErrorMessage(error)}`,
|
||||
},
|
||||
],
|
||||
details: { error: true },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue