update
This commit is contained in:
1 parent
9510e1c858
commit
6b47e95d36
19 files changed
+195
-1409
No files matched your search
+26
-2
@@ -10,6 +10,29 @@ import type { AppDatabase } from "./database";
|
||||
import { dispatchAgentMessage } from "./message-dispatch";
|
||||
import type { DesktopEvent } from "@shared/types";
|
||||
|
||||
/**
|
||||
* Decode an HTTP request body to a UTF-8 string, handling the common Windows
|
||||
* encoding issue where Chinese/CJK characters arrive in GBK (CP936) instead
|
||||
* of UTF-8. On Chinese Windows the system ANSI code page is GBK; tools like
|
||||
* curl encode command-line arguments using that code page when stdout is piped
|
||||
* (chcp 65001 does not help because there is no real console). We try strict
|
||||
* UTF-8 first and, only if the bytes are invalid UTF-8, fall back to GBK.
|
||||
*/
|
||||
function decodeBodyToUtf8(raw: Buffer): string {
|
||||
if (raw.length === 0) return "";
|
||||
try {
|
||||
return new TextDecoder("utf-8", { fatal: true }).decode(raw);
|
||||
} catch {
|
||||
// Bytes are not valid UTF-8 — most likely GBK from Chinese Windows.
|
||||
try {
|
||||
return new TextDecoder("gbk").decode(raw);
|
||||
} catch {
|
||||
// Last resort: lossy UTF-8 (replaces invalid bytes with U+FFFD).
|
||||
return raw.toString("utf8");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RequestHandler = (
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
@@ -234,7 +257,8 @@ export class AgentApiServer {
|
||||
});
|
||||
req.on("end", () => {
|
||||
try {
|
||||
const data = Buffer.concat(chunks).toString("utf8");
|
||||
const raw = Buffer.concat(chunks);
|
||||
const data = decodeBodyToUtf8(raw);
|
||||
resolve(data ? JSON.parse(data) : null);
|
||||
}
|
||||
catch { reject(new Error("Invalid JSON")); }
|
||||
@@ -244,7 +268,7 @@ export class AgentApiServer {
|
||||
}
|
||||
|
||||
private json(res: ServerResponse, status: number, data: unknown) {
|
||||
res.writeHead(status, { "Content-Type": "application/json" });
|
||||
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
||||
res.end(JSON.stringify(data));
|
||||
}
|
||||
|
||||
|
||||
+33
-4
@@ -76,6 +76,32 @@ function safeRealpath(targetPath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the last complete UTF-8 character boundary in a buffer.
|
||||
* Returns the number of bytes up to (and including) the last complete character.
|
||||
* This prevents corrupting multi-byte CJK characters when slicing at chunk edges.
|
||||
*/
|
||||
function findUtf8Boundary(buffer: Buffer, bytesRead: number): number {
|
||||
if (bytesRead === 0) return 0;
|
||||
// Walk backwards past continuation bytes (10xxxxxx)
|
||||
let pos = bytesRead - 1;
|
||||
while (pos >= 0 && (buffer[pos] & 0xc0) === 0x80) {
|
||||
pos--;
|
||||
}
|
||||
if (pos < 0) return bytesRead;
|
||||
const lead = buffer[pos];
|
||||
let charLen: number;
|
||||
if ((lead & 0x80) === 0) charLen = 1;
|
||||
else if ((lead & 0xe0) === 0xc0) charLen = 2;
|
||||
else if ((lead & 0xf0) === 0xe0) charLen = 3;
|
||||
else if ((lead & 0xf8) === 0xf0) charLen = 4;
|
||||
else return bytesRead;
|
||||
// If the character is complete within the buffer, keep all bytes
|
||||
if (pos + charLen <= bytesRead) return bytesRead;
|
||||
// Otherwise truncate before the incomplete character
|
||||
return pos;
|
||||
}
|
||||
|
||||
function taskPriorityOrderSql(columnName: string) {
|
||||
return `case ${columnName}
|
||||
when 'critical' then 4
|
||||
@@ -1156,14 +1182,14 @@ export class AppDatabase {
|
||||
.run(id, input.companyId, input.taskId, input.agentId, input.workspaceId, input.connectorId, logPath, ts, ts);
|
||||
this.db.prepare("update tasks set active_run_id = ?, updated_at = ? where id = ?").run(id, ts, input.taskId);
|
||||
});
|
||||
writeFileSync(logPath, "");
|
||||
writeFileSync(logPath, "", "utf8");
|
||||
return { id, logPath };
|
||||
}
|
||||
|
||||
appendRunLog(runId: string, stream: "stdout" | "stderr", chunk: string) {
|
||||
const run = this.db.prepare("select log_path as logPath from runs where id = ?").get(runId) as { logPath: string | null } | undefined;
|
||||
if (!run?.logPath) return;
|
||||
return appendFile(run.logPath, `[${new Date().toISOString()}][${stream}] ${chunk}`);
|
||||
return appendFile(run.logPath, `[${new Date().toISOString()}][${stream}] ${chunk}`, "utf8");
|
||||
}
|
||||
|
||||
readRunLog(runId: string) {
|
||||
@@ -1207,9 +1233,12 @@ export class AppDatabase {
|
||||
try {
|
||||
const buffer = Buffer.allocUnsafe(bytesToRead);
|
||||
const bytesRead = readSync(fileDescriptor, buffer, 0, bytesToRead, safeOffset);
|
||||
const nextOffset = safeOffset + bytesRead;
|
||||
// Find the last complete UTF-8 character boundary to avoid corrupting
|
||||
// multi-byte characters (e.g. Chinese/CJK 3-byte sequences) at the edge.
|
||||
const safeBytesRead = findUtf8Boundary(buffer, bytesRead);
|
||||
const nextOffset = safeOffset + safeBytesRead;
|
||||
return {
|
||||
content: buffer.subarray(0, bytesRead).toString("utf8"),
|
||||
content: buffer.subarray(0, safeBytesRead).toString("utf8"),
|
||||
offset: safeOffset,
|
||||
nextOffset,
|
||||
totalBytes,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { extname } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { resolve, sep } from "node:path";
|
||||
import {
|
||||
@@ -81,14 +82,33 @@ export function createDesktopShellService({
|
||||
}
|
||||
|
||||
function registerProtocol() {
|
||||
protocol.handle("app", (request) => {
|
||||
const textMimeTypes: Record<string, string> = {
|
||||
".html": "text/html; charset=utf-8",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".mjs": "text/javascript; charset=utf-8",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".svg": "image/svg+xml; charset=utf-8",
|
||||
};
|
||||
protocol.handle("app", async (request) => {
|
||||
const url = new URL(request.url);
|
||||
const relativePath = decodeURIComponent(url.pathname === "/" ? "index.html" : url.pathname.replace(/^\/+/, ""));
|
||||
const targetPath = resolve(rendererDist, relativePath);
|
||||
if (!isWithinDirectory(rendererDist, targetPath) || !existsSync(targetPath)) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
return net.fetch(pathToFileURL(targetPath).toString());
|
||||
const response = await net.fetch(pathToFileURL(targetPath).toString());
|
||||
const mime = textMimeTypes[extname(targetPath).toLowerCase()];
|
||||
if (mime) {
|
||||
const headers = new Headers(response.headers);
|
||||
headers.set("Content-Type", mime);
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+27
-18
@@ -451,38 +451,47 @@ Do not print or echo AGENT_COMPANY_API_KEY into comments, summaries, or external
|
||||
## Authentication & Request Format
|
||||
Every API call MUST include these headers:
|
||||
Authorization: Bearer $AGENT_COMPANY_API_KEY
|
||||
Content-Type: application/json
|
||||
Content-Type: application/json; charset=utf-8
|
||||
X-Agent-Company-Run-Id: ${ctx.runId}
|
||||
|
||||
Example using curl:
|
||||
curl -s -X GET "$AGENT_COMPANY_API_URL/api/agents/me" \\
|
||||
-H "Authorization: Bearer $AGENT_COMPANY_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "X-Agent-Company-Run-Id: ${ctx.runId}"
|
||||
IMPORTANT: On Windows, prefer using Node.js fetch() for API calls instead of curl,
|
||||
because curl on Windows may corrupt non-ASCII characters (Chinese, Japanese, etc.).
|
||||
If you must use curl, write the JSON body to a temp file first and use curl -d @file.
|
||||
|
||||
Example using fetch (Node.js):
|
||||
Example using fetch (Node.js) — PREFERRED:
|
||||
const res = await fetch(\`\${process.env.AGENT_COMPANY_API_URL}/api/agents/me\`, {
|
||||
headers: {
|
||||
"Authorization": \`Bearer \${process.env.AGENT_COMPANY_API_KEY}\`,
|
||||
"Content-Type": "application/json",
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"X-Agent-Company-Run-Id": "${ctx.runId}"
|
||||
}
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
Example POST using curl:
|
||||
Example POST using fetch (Node.js) — PREFERRED for non-ASCII content:
|
||||
const res = await fetch(\`\${process.env.AGENT_COMPANY_API_URL}/api/companies/${ctx.agent.companyId}/issues\`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": \`Bearer \${process.env.AGENT_COMPANY_API_KEY}\`,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"X-Agent-Company-Run-Id": "${ctx.runId}"
|
||||
},
|
||||
body: JSON.stringify({title:"Task title",description:"Details",assigneeAgentId:"agent-id",priority:"medium",status:"todo"})
|
||||
});
|
||||
|
||||
Example GET using curl:
|
||||
curl -s -X GET "$AGENT_COMPANY_API_URL/api/agents/me" \\
|
||||
-H "Authorization: Bearer $AGENT_COMPANY_API_KEY" \\
|
||||
-H "Content-Type: application/json; charset=utf-8" \\
|
||||
-H "X-Agent-Company-Run-Id: ${ctx.runId}"
|
||||
|
||||
Example POST using curl (write body to temp file to avoid encoding issues):
|
||||
echo '{"title":"Task title","description":"Details","priority":"medium","status":"todo"}' > /tmp/body.json
|
||||
curl -s -X POST "$AGENT_COMPANY_API_URL/api/companies/${ctx.agent.companyId}/issues" \\
|
||||
-H "Authorization: Bearer $AGENT_COMPANY_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "Content-Type: application/json; charset=utf-8" \\
|
||||
-H "X-Agent-Company-Run-Id: ${ctx.runId}" \\
|
||||
-d '{"title":"Task title","description":"Details","assigneeAgentId":"agent-id","priority":"medium","status":"todo"}'
|
||||
|
||||
Example PATCH using curl:
|
||||
curl -s -X PATCH "$AGENT_COMPANY_API_URL/api/issues/ISSUE_ID" \\
|
||||
-H "Authorization: Bearer $AGENT_COMPANY_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-H "X-Agent-Company-Run-Id: ${ctx.runId}" \\
|
||||
-d '{"status":"in_review","comment":"Work completed. Summary of changes: ..."}'
|
||||
-d @/tmp/body.json
|
||||
|
||||
IMPORTANT: Always check the HTTP response status. 200/201 = success, 4xx = client error (read the error message), 5xx = server error (retry once then report).
|
||||
|
||||
|
||||
Reference in new issue
Block a user