@yuandc/aica 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/agent.js +1 -54
- package/dist/acp/client/acp-client.js +1 -102
- package/dist/acp/client/acp-content.js +1 -13
- package/dist/acp/client/acp-events.js +1 -106
- package/dist/acp/client/acp-process.js +1 -34
- package/dist/acp/client/acp-runtime-pool.js +1 -248
- package/dist/acp/client/context-usage.js +1 -29
- package/dist/acp/client/json-rpc.js +4 -128
- package/dist/acp/provider-types.js +0 -1
- package/dist/acp/providers/codex/codex-process.js +1 -51
- package/dist/acp/providers/codex/events.js +28 -1473
- package/dist/acp/providers/codex/permissions.js +1 -49
- package/dist/acp/providers/codex/provider.js +1 -376
- package/dist/acp/providers/codex-acp/adapter.js +5 -947
- package/dist/acp/providers/codex-acp/context-maintenance.js +5 -148
- package/dist/acp/providers/codex-acp/launch.js +1 -35
- package/dist/acp/providers/codex-acp/provider.js +1 -486
- package/dist/acp/providers/mimo/provider.js +5 -448
- package/dist/acp/providers/opencode/provider.js +4 -489
- package/dist/acp/providers/registry.js +1 -23
- package/dist/acp/standard-events.js +1 -167
- package/dist/commands/start.js +1 -137
- package/dist/commands/worker-auth.js +4 -100
- package/dist/commands/worker-project.js +1 -57
- package/dist/core/aca-config.js +1 -74
- package/dist/core/aca-server-client.js +1 -57
- package/dist/core/acp-event-coalescer.js +1 -108
- package/dist/core/acp-event-upload-filter.js +1 -16
- package/dist/core/acp-orphan-cleanup.js +1 -91
- package/dist/core/affected-files.js +2 -268
- package/dist/core/auth.js +1 -36
- package/dist/core/file-transfer-worker.js +1 -169
- package/dist/core/fs.js +2 -28
- package/dist/core/heartbeat.js +3 -578
- package/dist/core/job-permission-policy.js +1 -42
- package/dist/core/job-worker.js +6 -749
- package/dist/core/logger.js +3 -42
- package/dist/core/long-poll-worker.js +1 -26
- package/dist/core/machine-filesystem-worker.js +3 -352
- package/dist/core/paths.js +1 -26
- package/dist/core/process-identity.js +1 -34
- package/dist/core/process.js +2 -33
- package/dist/core/provider-health.js +1 -54
- package/dist/core/runtime-options.js +1 -38
- package/dist/core/worktree.js +1 -95
- package/dist/worker-cli.js +1 -26
- package/dist/worker-single-cli.js +1 -16
- package/package.json +1 -1
|
@@ -1,169 +1 @@
|
|
|
1
|
-
import fs from "node:
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { Readable } from "node:stream";
|
|
5
|
-
import { acaServerRequest, acaServerStreamRequest } from "./aca-server-client.js";
|
|
6
|
-
import { loadAcaConfig } from "./aca-config.js";
|
|
7
|
-
import { getAcaHome } from "./paths.js";
|
|
8
|
-
import { startLongPollWorker } from "./long-poll-worker.js";
|
|
9
|
-
const LONG_POLL_WAIT_MS = 25_000;
|
|
10
|
-
export function startFileTransferWorkerLoop(logger) {
|
|
11
|
-
return startLongPollWorker({
|
|
12
|
-
run: async () => {
|
|
13
|
-
const config = loadAcaConfig();
|
|
14
|
-
if (!config.token)
|
|
15
|
-
throw new Error("Worker 尚未配置认证令牌");
|
|
16
|
-
const response = await acaServerRequest("GET", `/api/client/file-requests/claim?machineId=${encodeURIComponent(config.machineId)}&waitMs=${LONG_POLL_WAIT_MS}`);
|
|
17
|
-
if (!response.item)
|
|
18
|
-
return;
|
|
19
|
-
try {
|
|
20
|
-
await streamFileRequest(response.item, config, logger);
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
-
logger.warn(`file request failed request=${response.item.requestId}: ${message}`);
|
|
25
|
-
try {
|
|
26
|
-
await failFileRequest(response.item.requestId, message);
|
|
27
|
-
}
|
|
28
|
-
catch (failError) {
|
|
29
|
-
logger.warn(`file request fail callback failed request=${response.item.requestId}: ${failError instanceof Error ? failError.message : String(failError)}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
onError: (error, retryMs) => logger.warn(`file transfer worker failed, retry in ${retryMs}ms: ${error instanceof Error ? error.message : String(error)}`)
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
async function failFileRequest(requestId, message) {
|
|
37
|
-
await acaServerRequest("POST", `/api/client/file-requests/${encodeURIComponent(requestId)}/fail`, { message });
|
|
38
|
-
}
|
|
39
|
-
async function streamFileRequest(request, config, logger) {
|
|
40
|
-
const project = config.projects.find((item) => item.projectId === request.projectId);
|
|
41
|
-
const projectRoot = expandProjectRoot(request.projectRoot || project?.rootPath || "");
|
|
42
|
-
if (!projectRoot)
|
|
43
|
-
throw new Error(`file request ${request.requestId} project root is unknown`);
|
|
44
|
-
const purpose = request.purpose === "temp-image" ? "temp-image" : "project-file";
|
|
45
|
-
const resolved = purpose === "temp-image"
|
|
46
|
-
? resolveTempImageFile(request.path)
|
|
47
|
-
: resolveProjectFile(projectRoot, request.path);
|
|
48
|
-
const stat = fs.statSync(resolved);
|
|
49
|
-
if (!stat.isFile())
|
|
50
|
-
throw new Error(`file request ${request.requestId} target is not a file`);
|
|
51
|
-
if (stat.size > request.maxBytes)
|
|
52
|
-
throw new Error(`file request ${request.requestId} target is too large (${stat.size} bytes)`);
|
|
53
|
-
const mimeType = purpose === "temp-image" ? detectSafeTempImageMime(resolved) : mimeForPath(resolved);
|
|
54
|
-
logger.info(`streaming file request=${request.requestId} path=${resolved}`);
|
|
55
|
-
await acaServerStreamRequest("POST", `/api/client/file-requests/${encodeURIComponent(request.requestId)}/stream`, Readable.toWeb(fs.createReadStream(resolved)), {
|
|
56
|
-
"content-type": "application/octet-stream",
|
|
57
|
-
"x-aca-file-name": encodeURIComponent(path.basename(resolved)),
|
|
58
|
-
"x-aca-file-size": String(stat.size),
|
|
59
|
-
"x-aca-file-mime": mimeType
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
function resolveProjectFile(projectRoot, requestedPath) {
|
|
63
|
-
const root = fs.realpathSync(expandProjectRoot(projectRoot));
|
|
64
|
-
const candidate = path.isAbsolute(requestedPath)
|
|
65
|
-
? requestedPath
|
|
66
|
-
: path.join(root, requestedPath);
|
|
67
|
-
const target = fs.realpathSync(candidate);
|
|
68
|
-
const relative = path.relative(root, target);
|
|
69
|
-
if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
70
|
-
throw new Error("requested file is outside project root");
|
|
71
|
-
}
|
|
72
|
-
return target;
|
|
73
|
-
}
|
|
74
|
-
export function resolveTempImageFile(requestedPath) {
|
|
75
|
-
const candidate = localPathFromUri(requestedPath);
|
|
76
|
-
if (!path.isAbsolute(candidate))
|
|
77
|
-
throw new Error("temporary image path must be absolute");
|
|
78
|
-
const sourceStat = fs.lstatSync(candidate);
|
|
79
|
-
if (sourceStat.isSymbolicLink())
|
|
80
|
-
throw new Error("temporary image cannot be a symbolic link");
|
|
81
|
-
if (!sourceStat.isFile())
|
|
82
|
-
throw new Error("temporary image target is not a regular file");
|
|
83
|
-
const tempRoot = fs.realpathSync(os.tmpdir());
|
|
84
|
-
const target = fs.realpathSync(candidate);
|
|
85
|
-
const relative = path.relative(tempRoot, target);
|
|
86
|
-
if (relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
87
|
-
throw new Error("temporary image is outside the system temp directory");
|
|
88
|
-
}
|
|
89
|
-
return target;
|
|
90
|
-
}
|
|
91
|
-
export function detectSafeTempImageMime(filePath) {
|
|
92
|
-
const extension = path.extname(filePath).toLowerCase();
|
|
93
|
-
const header = Buffer.alloc(16);
|
|
94
|
-
const file = fs.openSync(filePath, "r");
|
|
95
|
-
let bytesRead = 0;
|
|
96
|
-
try {
|
|
97
|
-
bytesRead = fs.readSync(file, header, 0, header.length, 0);
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
fs.closeSync(file);
|
|
101
|
-
}
|
|
102
|
-
const bytes = header.subarray(0, bytesRead);
|
|
103
|
-
if (extension === ".png" && bytes.length >= 8 && bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
|
|
104
|
-
return "image/png";
|
|
105
|
-
}
|
|
106
|
-
if ([".jpg", ".jpeg"].includes(extension) && bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
107
|
-
return "image/jpeg";
|
|
108
|
-
}
|
|
109
|
-
if (extension === ".gif" && bytes.length >= 6 && ["GIF87a", "GIF89a"].includes(bytes.subarray(0, 6).toString("ascii"))) {
|
|
110
|
-
return "image/gif";
|
|
111
|
-
}
|
|
112
|
-
if (extension === ".webp" && bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
113
|
-
return "image/webp";
|
|
114
|
-
}
|
|
115
|
-
throw new Error("temporary file is not a supported raster image");
|
|
116
|
-
}
|
|
117
|
-
function localPathFromUri(value) {
|
|
118
|
-
const trimmed = String(value || "").trim();
|
|
119
|
-
if (!trimmed.startsWith("file://"))
|
|
120
|
-
return trimmed;
|
|
121
|
-
try {
|
|
122
|
-
return decodeURIComponent(new URL(trimmed).pathname);
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
throw new Error("temporary image path is invalid");
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
function expandProjectRoot(value) {
|
|
129
|
-
if (!value)
|
|
130
|
-
return "";
|
|
131
|
-
if (value === "~")
|
|
132
|
-
return path.dirname(getAcaHome());
|
|
133
|
-
if (value.startsWith("~/"))
|
|
134
|
-
return path.join(path.dirname(getAcaHome()), value.slice(2));
|
|
135
|
-
return value;
|
|
136
|
-
}
|
|
137
|
-
function mimeForPath(filePath) {
|
|
138
|
-
switch (path.extname(filePath).toLowerCase()) {
|
|
139
|
-
case ".pdf":
|
|
140
|
-
return "application/pdf";
|
|
141
|
-
case ".md":
|
|
142
|
-
case ".markdown":
|
|
143
|
-
return "text/markdown; charset=utf-8";
|
|
144
|
-
case ".txt":
|
|
145
|
-
case ".log":
|
|
146
|
-
return "text/plain; charset=utf-8";
|
|
147
|
-
case ".html":
|
|
148
|
-
return "text/html; charset=utf-8";
|
|
149
|
-
case ".json":
|
|
150
|
-
return "application/json; charset=utf-8";
|
|
151
|
-
case ".png":
|
|
152
|
-
return "image/png";
|
|
153
|
-
case ".jpg":
|
|
154
|
-
case ".jpeg":
|
|
155
|
-
return "image/jpeg";
|
|
156
|
-
case ".gif":
|
|
157
|
-
return "image/gif";
|
|
158
|
-
case ".webp":
|
|
159
|
-
return "image/webp";
|
|
160
|
-
case ".zip":
|
|
161
|
-
return "application/zip";
|
|
162
|
-
case ".docx":
|
|
163
|
-
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
164
|
-
case ".xlsx":
|
|
165
|
-
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
166
|
-
default:
|
|
167
|
-
return "application/octet-stream";
|
|
168
|
-
}
|
|
169
|
-
}
|
|
1
|
+
import s from"node:fs";import g from"node:os";import i from"node:path";import{Readable as h}from"node:stream";import{acaServerRequest as p,acaServerStreamRequest as w}from"./aca-server-client.js";import{loadAcaConfig as y}from"./aca-config.js";import{getAcaHome as f}from"./paths.js";import{startLongPollWorker as x}from"./long-poll-worker.js";const S=25e3;function P(e){return x({run:async()=>{const t=y();if(!t.token)throw new Error("Worker 尚未配置认证令牌");const a=await p("GET",`/api/client/file-requests/claim?machineId=${encodeURIComponent(t.machineId)}&waitMs=${S}`);if(a.item)try{await I(a.item,t,e)}catch(n){const o=n instanceof Error?n.message:String(n);e.warn(`file request failed request=${a.item.requestId}: ${o}`);try{await b(a.item.requestId,o)}catch(r){e.warn(`file request fail callback failed request=${a.item.requestId}: ${r instanceof Error?r.message:String(r)}`)}}},onError:(t,a)=>e.warn(`file transfer worker failed, retry in ${a}ms: ${t instanceof Error?t.message:String(t)}`)})}async function b(e,t){await p("POST",`/api/client/file-requests/${encodeURIComponent(e)}/fail`,{message:t})}async function I(e,t,a){const n=t.projects.find(d=>d.projectId===e.projectId),o=l(e.projectRoot||n?.rootPath||"");if(!o)throw new Error(`file request ${e.requestId} project root is unknown`);const r=e.purpose==="temp-image"?"temp-image":"project-file",c=r==="temp-image"?E(e.path):j(o,e.path),m=s.statSync(c);if(!m.isFile())throw new Error(`file request ${e.requestId} target is not a file`);if(m.size>e.maxBytes)throw new Error(`file request ${e.requestId} target is too large (${m.size} bytes)`);const u=r==="temp-image"?R(c):q(c);a.info(`streaming file request=${e.requestId} path=${c}`),await w("POST",`/api/client/file-requests/${encodeURIComponent(e.requestId)}/stream`,h.toWeb(s.createReadStream(c)),{"content-type":"application/octet-stream","x-aca-file-name":encodeURIComponent(i.basename(c)),"x-aca-file-size":String(m.size),"x-aca-file-mime":u})}function j(e,t){const a=s.realpathSync(l(e)),n=i.isAbsolute(t)?t:i.join(a,t),o=s.realpathSync(n),r=i.relative(a,o);if(r===""||r.startsWith("..")||i.isAbsolute(r))throw new Error("requested file is outside project root");return o}function E(e){const t=$(e);if(!i.isAbsolute(t))throw new Error("temporary image path must be absolute");const a=s.lstatSync(t);if(a.isSymbolicLink())throw new Error("temporary image cannot be a symbolic link");if(!a.isFile())throw new Error("temporary image target is not a regular file");const n=s.realpathSync(g.tmpdir()),o=s.realpathSync(t),r=i.relative(n,o);if(r===""||r.startsWith("..")||i.isAbsolute(r))throw new Error("temporary image is outside the system temp directory");return o}function R(e){const t=i.extname(e).toLowerCase(),a=Buffer.alloc(16),n=s.openSync(e,"r");let o=0;try{o=s.readSync(n,a,0,a.length,0)}finally{s.closeSync(n)}const r=a.subarray(0,o);if(t===".png"&&r.length>=8&&r.subarray(0,8).equals(Buffer.from([137,80,78,71,13,10,26,10])))return"image/png";if([".jpg",".jpeg"].includes(t)&&r.length>=3&&r[0]===255&&r[1]===216&&r[2]===255)return"image/jpeg";if(t===".gif"&&r.length>=6&&["GIF87a","GIF89a"].includes(r.subarray(0,6).toString("ascii")))return"image/gif";if(t===".webp"&&r.length>=12&&r.subarray(0,4).toString("ascii")==="RIFF"&&r.subarray(8,12).toString("ascii")==="WEBP")return"image/webp";throw new Error("temporary file is not a supported raster image")}function $(e){const t=String(e||"").trim();if(!t.startsWith("file://"))return t;try{return decodeURIComponent(new URL(t).pathname)}catch{throw new Error("temporary image path is invalid")}}function l(e){return e?e==="~"?i.dirname(f()):e.startsWith("~/")?i.join(i.dirname(f()),e.slice(2)):e:""}function q(e){switch(i.extname(e).toLowerCase()){case".pdf":return"application/pdf";case".md":case".markdown":return"text/markdown; charset=utf-8";case".txt":case".log":return"text/plain; charset=utf-8";case".html":return"text/html; charset=utf-8";case".json":return"application/json; charset=utf-8";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".gif":return"image/gif";case".webp":return"image/webp";case".zip":return"application/zip";case".docx":return"application/vnd.openxmlformats-officedocument.wordprocessingml.document";case".xlsx":return"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";default:return"application/octet-stream"}}export{R as detectSafeTempImageMime,E as resolveTempImageFile,P as startFileTransferWorkerLoop};
|
package/dist/core/fs.js
CHANGED
|
@@ -1,28 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export function ensureDir(dir) {
|
|
4
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
5
|
-
}
|
|
6
|
-
export function writeJsonFile(file, value) {
|
|
7
|
-
ensureDir(path.dirname(file));
|
|
8
|
-
fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
9
|
-
}
|
|
10
|
-
export function readJsonFile(file) {
|
|
11
|
-
try {
|
|
12
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
if (error.code === "ENOENT")
|
|
16
|
-
return null;
|
|
17
|
-
throw error;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export function removeFileIfExists(file) {
|
|
21
|
-
try {
|
|
22
|
-
fs.unlinkSync(file);
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
if (error.code !== "ENOENT")
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
import t from"node:fs";import n from"node:path";function i(r){t.mkdirSync(r,{recursive:!0})}function u(r,e){i(n.dirname(r)),t.writeFileSync(r,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,"utf8")}function f(r){try{return JSON.parse(t.readFileSync(r,"utf8"))}catch(e){if(e.code==="ENOENT")return null;throw e}}function l(r){try{t.unlinkSync(r)}catch(e){if(e.code!=="ENOENT")throw e}}export{i as ensureDir,f as readJsonFile,l as removeFileIfExists,u as writeJsonFile};
|