更新tjwater-cli skill和环境
This commit is contained in:
@@ -47,6 +47,8 @@ const envSchema = z
|
||||
OPENCODE_CLIENT_BASE_URL: z.string().url().optional(),
|
||||
// 提供给本地 opencode tools 读取的会话上下文目录。
|
||||
SESSION_CONTEXT_STORAGE_DIR: z.string().default("./data/session-contexts"),
|
||||
// tjwater-cli 可执行文件路径。
|
||||
TJWATER_CLI_PATH: z.string().default("./cli/tjwater-cli"),
|
||||
// TJWater 后端 API 的基础地址。
|
||||
TJWATER_API_BASE_URL: z.string().default("http://127.0.0.1:8000"),
|
||||
// 代理调用 TJWater 后端 API 的超时时间(毫秒)。
|
||||
|
||||
+113
@@ -1,4 +1,5 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { spawn } from "node:child_process";
|
||||
import cors from "cors";
|
||||
import express from "express";
|
||||
|
||||
@@ -114,6 +115,118 @@ app.post("/internal/tools/dynamic-http-call", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/internal/tools/tjwater-cli-call", async (req, res) => {
|
||||
if (req.header("x-agent-internal-token") !== internalToken) {
|
||||
res.status(403).json({ message: "forbidden" });
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionScopeKey =
|
||||
typeof req.body?.sessionScopeKey === "string" ? req.body.sessionScopeKey : "";
|
||||
const threadContext = await toolContextStore.read(sessionScopeKey);
|
||||
const runtimeContext = sessionBridge.getActiveSensitiveContext(sessionScopeKey);
|
||||
if (!threadContext && !runtimeContext) {
|
||||
res.status(404).json({
|
||||
message: "runtime or session context not found",
|
||||
detail: sessionScopeKey,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const context = runtimeContext ?? threadContext;
|
||||
if (!context) {
|
||||
res.status(404).json({
|
||||
message: "runtime or session context not found",
|
||||
detail: sessionScopeKey,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const command = typeof req.body?.command === "string" ? req.body.command.trim() : "";
|
||||
if (!command) {
|
||||
res.status(400).json({ message: "command is required" });
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutSec =
|
||||
typeof req.body?.timeout === "number" && req.body.timeout > 0 ? req.body.timeout : 60;
|
||||
|
||||
const authJson = JSON.stringify({
|
||||
server: config.TJWATER_API_BASE_URL,
|
||||
access_token: runtimeContext?.accessToken,
|
||||
project_id: context.projectId,
|
||||
network:"tjwater",
|
||||
});
|
||||
|
||||
const cliArgs = ["--auth-stdin", ...command.split(/\s+/).filter(Boolean)];
|
||||
|
||||
const child = spawn(config.TJWATER_CLI_PATH, cliArgs, {
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout.on("data", (data: Buffer) => {
|
||||
stdout += data.toString("utf-8");
|
||||
});
|
||||
child.stderr.on("data", (data: Buffer) => {
|
||||
stderr += data.toString("utf-8");
|
||||
});
|
||||
|
||||
child.stdin.write(authJson);
|
||||
child.stdin.end();
|
||||
|
||||
const exitCode = await new Promise<number | null>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
child.kill("SIGTERM");
|
||||
resolve(-1);
|
||||
}, timeoutSec * 1000);
|
||||
child.on("close", (code) => {
|
||||
clearTimeout(timer);
|
||||
resolve(code);
|
||||
});
|
||||
child.on("error", (err) => {
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
if (exitCode === -1) {
|
||||
res.status(504).json({
|
||||
ok: false,
|
||||
schema_version: "tjwater-cli/v1",
|
||||
summary: "命令超时",
|
||||
error: {
|
||||
code: "TIMEOUT",
|
||||
message: `command timed out after ${timeoutSec}s`,
|
||||
retryable: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (exitCode !== 0) {
|
||||
res.status(502).json({
|
||||
ok: false,
|
||||
exit_code: exitCode,
|
||||
stderr: stderr.slice(0, 2000),
|
||||
stdout: stdout.slice(0, 2000),
|
||||
message: `CLI exited with code ${exitCode}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
res.json(JSON.parse(stdout));
|
||||
} catch {
|
||||
res.json({
|
||||
ok: true,
|
||||
schema_version: "tjwater-cli/v1",
|
||||
raw: stdout,
|
||||
stderr: stderr || undefined,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/internal/tools/fetch-result-ref", async (req, res) => {
|
||||
if (req.header("x-agent-internal-token") !== internalToken) {
|
||||
res.status(403).json({ message: "forbidden" });
|
||||
|
||||
Reference in New Issue
Block a user