autowonder 0.2.131 → 0.2.133
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/README.md +25 -7
- package/bin/cli.js +100 -0
- package/lib/workitem-upload.js +285 -0
- package/package.json +2 -1
- package/vendor/autowonder-daemon-darwin-amd64 +0 -0
- package/vendor/autowonder-daemon-darwin-arm64 +0 -0
- package/vendor/autowonder-daemon-linux-amd64 +0 -0
- package/vendor/autowonder-daemon-linux-arm64 +0 -0
- package/vendor/autowonder-daemon-win32-amd64.exe +0 -0
- package/vendor/autowonder-daemon-win32-arm64.exe +0 -0
package/README.md
CHANGED
|
@@ -6,16 +6,16 @@ AutoWonder 本地 agent runtime。安装后启动 daemon,持续轮询本地 as
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
# Qoder CLI(模型使用 provider model ID;Context Window 使用固定档位)
|
|
9
|
-
npx -y autowonder@0.2.
|
|
9
|
+
npx -y autowonder@0.2.133 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider qoder --model qmodel_latest --reasoning-effort medium --context-window 260000
|
|
10
10
|
|
|
11
11
|
# Qoder CLI CN
|
|
12
|
-
npx -y autowonder@0.2.
|
|
12
|
+
npx -y autowonder@0.2.133 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider qodercn
|
|
13
13
|
|
|
14
14
|
# Claude Code
|
|
15
|
-
npx -y autowonder@0.2.
|
|
15
|
+
npx -y autowonder@0.2.133 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider claude
|
|
16
16
|
|
|
17
17
|
# Codex CLI
|
|
18
|
-
npx -y autowonder@0.2.
|
|
18
|
+
npx -y autowonder@0.2.133 connect --ws-url <wss-endpoint> --token <executor-token> --executor-id <executor-id> --provider codex --model gpt-5.5 --reasoning-effort medium
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
`connect` 会安装当前 npm 包内置的 daemon,并使用页面生成的 WebSocket endpoint、Token 和执行器 ID 建立连接。选择 Qoder 时需要 Node.js 20+;runtime 会在缺少 `qodercli` 时自动通过 npm 安装,并在尚未登录时打开 Qoder 浏览器登录。Claude Code 和 Codex CLI 仍需提前安装并登录。runtime 会继承当前用户的 HOME、环境变量和 CLI 登录状态。
|
|
@@ -62,14 +62,32 @@ mv "$queue/.assignment.tmp" "$queue/assignment.json"
|
|
|
62
62
|
也可以直接提交到本地 API:
|
|
63
63
|
|
|
64
64
|
```bash
|
|
65
|
-
npx -y autowonder@0.2.
|
|
65
|
+
npx -y autowonder@0.2.133 dispatch ./assignment.json
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
## 上传工单附件
|
|
69
|
+
|
|
70
|
+
把本地需求/设计文档直接上传到工单,避免在会话里传递大文件:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx -y autowonder@0.2.133 workitem upload --server-url <autowonder-server-url> --workitem-id <id> --file <filepath-1> --file <filepath-2> --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
上传令牌通过 MCP 工具 `autowonder.workitem_cli_upload_token` 签发,经 `--token` 或 `AUTOWONDER_UPLOAD_TOKEN` 环境变量传入。`--file` 可重复指定多个文件;`--json` 输出机器可读结果。失败时按错误类型返回不同退出码(401→3、403→4、404→5、409→6、413→7、其他 4xx→8、网络/重定向→9)。
|
|
77
|
+
|
|
68
78
|
## 管理 daemon
|
|
69
79
|
|
|
70
80
|
```bash
|
|
71
|
-
npx -y autowonder@0.2.
|
|
72
|
-
npx -y autowonder@0.2.
|
|
81
|
+
npx -y autowonder@0.2.133 status
|
|
82
|
+
npx -y autowonder@0.2.133 stop
|
|
73
83
|
```
|
|
74
84
|
|
|
75
85
|
默认 API 是 `http://127.0.0.1:34989`,日志位于 `~/.autowonder/daemon.log`。npm 包不包含任何 agent、MCP 或服务端凭证。
|
|
86
|
+
|
|
87
|
+
## 调试
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx -y autowonder@0.2.133 connect ... --debug
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`--debug` 会让 CLI 以 `AUTOWONDER_DEBUG=1` 启动 daemon,把完整的 daemon 与 agent 活动(runtime 事件流、provider 事件解码、重试退避与会话门等待、空闲时的阻塞点)同时输出到控制台和会话调试日志。调试日志不做脱敏,仅用于本机排障;不传 `--debug` 时该环境变量不会注入。
|
package/bin/cli.js
CHANGED
|
@@ -617,6 +617,7 @@ async function cmdConnect(args) {
|
|
|
617
617
|
if (flags["reasoning-effort"]) env.AUTOWONDER_REASONING_EFFORT = flags["reasoning-effort"];
|
|
618
618
|
if (flags["context-window"]) env.AUTOWONDER_CONTEXT_WINDOW = flags["context-window"];
|
|
619
619
|
if (config.claudeSettingsPath) env.AUTOWONDER_CLAUDE_SETTINGS = config.claudeSettingsPath;
|
|
620
|
+
if (flags.debug) env.AUTOWONDER_DEBUG = "1";
|
|
620
621
|
|
|
621
622
|
const child = spawn(DAEMON_BIN, [], { env, stdio: "inherit" });
|
|
622
623
|
child.on("exit", (code) => process.exit(code || 0));
|
|
@@ -807,6 +808,94 @@ async function cmdDispatch(args) {
|
|
|
807
808
|
}
|
|
808
809
|
}
|
|
809
810
|
|
|
811
|
+
async function cmdWorkitem(args) {
|
|
812
|
+
const sub = args[0];
|
|
813
|
+
if (sub === "upload") {
|
|
814
|
+
return cmdWorkitemUpload(args.slice(1));
|
|
815
|
+
}
|
|
816
|
+
error(`Unknown workitem command: ${sub || "(none)"}`);
|
|
817
|
+
cmdHelp();
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function uploadExitCode(err) {
|
|
822
|
+
if (err && err.statusCode) {
|
|
823
|
+
switch (err.statusCode) {
|
|
824
|
+
case 401: return 3;
|
|
825
|
+
case 403: return 4;
|
|
826
|
+
case 404: return 5;
|
|
827
|
+
case 409: return 6;
|
|
828
|
+
case 413: return 7;
|
|
829
|
+
}
|
|
830
|
+
if (err.statusCode >= 400) return 8;
|
|
831
|
+
return 9;
|
|
832
|
+
}
|
|
833
|
+
if (err && (err.kind === "network" || err.kind === "redirect")) return 9;
|
|
834
|
+
return 2;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function reportUploadFailure(err, json) {
|
|
838
|
+
if (json) {
|
|
839
|
+
const payload = { success: false, error: err.message };
|
|
840
|
+
if (err.statusCode) payload.status = err.statusCode;
|
|
841
|
+
if (err.kind) payload.kind = err.kind;
|
|
842
|
+
if (err.serverError) payload.server = err.serverError;
|
|
843
|
+
console.log(JSON.stringify(payload));
|
|
844
|
+
} else {
|
|
845
|
+
error(err.message);
|
|
846
|
+
if (err.serverError && (err.serverError.message || err.serverError.code)) {
|
|
847
|
+
error(`server: ${err.serverError.message || "rejected"} (code ${err.serverError.code})`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async function cmdWorkitemUpload(args) {
|
|
853
|
+
const upload = require("../lib/workitem-upload");
|
|
854
|
+
const json = args.includes("--json");
|
|
855
|
+
try {
|
|
856
|
+
const parsed = upload.parseUploadFlags(args);
|
|
857
|
+
const serverUrl = upload.validateServerUrl(parsed.serverUrl);
|
|
858
|
+
const workitemId = upload.validateWorkitemId(parsed.workitemId);
|
|
859
|
+
const token = parsed.token || process.env.AUTOWONDER_UPLOAD_TOKEN;
|
|
860
|
+
if (!token) {
|
|
861
|
+
throw new upload.UploadError(
|
|
862
|
+
"no upload token provided. Invoke the MCP tool autowonder.workitem_cli_upload_token to mint one, " +
|
|
863
|
+
"then pass it via --token or the AUTOWONDER_UPLOAD_TOKEN environment variable.",
|
|
864
|
+
{ kind: "token" },
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
const files = upload.preflightFiles(parsed.files);
|
|
868
|
+
|
|
869
|
+
const { statusCode, body } = await upload.uploadWorkitemDocuments({
|
|
870
|
+
serverUrl, workitemId, files, token,
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
if (statusCode === 200) {
|
|
874
|
+
let parsedBody = null;
|
|
875
|
+
try { parsedBody = JSON.parse(body); } catch { /* non-JSON success bodies are ignored */ }
|
|
876
|
+
if (parsed.json) {
|
|
877
|
+
console.log(JSON.stringify({
|
|
878
|
+
success: true,
|
|
879
|
+
workitemId: Number(workitemId),
|
|
880
|
+
uploaded: files.map((f) => f.filename),
|
|
881
|
+
response: parsedBody,
|
|
882
|
+
}));
|
|
883
|
+
} else {
|
|
884
|
+
log(`Uploaded ${files.length} file(s) to workitem ${workitemId}: ${files.map((f) => f.filename).join(", ")}`);
|
|
885
|
+
}
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
throw new upload.UploadError(
|
|
890
|
+
`server rejected the upload (HTTP ${statusCode})`,
|
|
891
|
+
{ kind: "http", statusCode, serverError: upload.parseServerError(body) },
|
|
892
|
+
);
|
|
893
|
+
} catch (err) {
|
|
894
|
+
reportUploadFailure(err, json);
|
|
895
|
+
process.exit(uploadExitCode(err));
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
810
899
|
async function cmdInstall(args) {
|
|
811
900
|
const flags = parseFlags(args, ["force", "provider", "max-tasks", "addr"]);
|
|
812
901
|
const force = flags.force === "true" || args.includes("--force");
|
|
@@ -829,6 +918,8 @@ function cmdHelp() {
|
|
|
829
918
|
autowonder status Show daemon status
|
|
830
919
|
autowonder dispatch <assignment.json> Submit a dispatch
|
|
831
920
|
autowonder install [--force] Install/update and start daemon
|
|
921
|
+
autowonder workitem upload --server-url <url> --workitem-id <id> --file <path> [--file <path>...]
|
|
922
|
+
Upload local requirement/design files to a workitem
|
|
832
923
|
|
|
833
924
|
Options:
|
|
834
925
|
--provider <name> Agent provider: claude, codex, qoder, qodercn (default: auto-detect; qodercn must be explicit)
|
|
@@ -843,6 +934,11 @@ function cmdHelp() {
|
|
|
843
934
|
--settings <file> Claude settings JSON file
|
|
844
935
|
--memory-mode <mode> Memory: platform, provider-local, or none (default: platform)
|
|
845
936
|
--name <name> Runtime name (default: provider + executor ID)
|
|
937
|
+
--debug Stream full daemon and agent activity to console + log file
|
|
938
|
+
--server-url <url> AutoWonder server base URL (workitem upload)
|
|
939
|
+
--workitem-id <id> Target workitem ID (workitem upload)
|
|
940
|
+
--file <path> Local file to upload, repeatable (workitem upload)
|
|
941
|
+
--json Machine-readable output (workitem upload)
|
|
846
942
|
-h, --help Show this help
|
|
847
943
|
|
|
848
944
|
Examples:
|
|
@@ -853,6 +949,9 @@ function cmdHelp() {
|
|
|
853
949
|
npx -y autowonder start
|
|
854
950
|
npx -y autowonder dispatch ./assignment.json
|
|
855
951
|
|
|
952
|
+
# Upload requirement/design documents from local files
|
|
953
|
+
npx -y autowonder@0.2.130 workitem upload --server-url https://private-autowonder.example.com --workitem-id 50063 --file <filepath-1> --file <filepath-2> --file <images-1> --json
|
|
954
|
+
|
|
856
955
|
# Check what's happening
|
|
857
956
|
npx -y autowonder status
|
|
858
957
|
`);
|
|
@@ -890,6 +989,7 @@ async function main() {
|
|
|
890
989
|
case "status": return cmdStatus();
|
|
891
990
|
case "dispatch": return cmdDispatch(commandArgs);
|
|
892
991
|
case "install": return cmdInstall(commandArgs);
|
|
992
|
+
case "workitem": return cmdWorkitem(commandArgs);
|
|
893
993
|
case "help":
|
|
894
994
|
case "--help":
|
|
895
995
|
case "-h": return cmdHelp();
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Workitem requirement/design document upload: streaming multipart over
|
|
4
|
+
// Node >= 16 built-ins only. Never Base64 encodes and never buffers whole
|
|
5
|
+
// file bodies; the server is authoritative for validation.
|
|
6
|
+
|
|
7
|
+
const crypto = require("node:crypto");
|
|
8
|
+
const fs = require("node:fs");
|
|
9
|
+
const http = require("node:http");
|
|
10
|
+
const https = require("node:https");
|
|
11
|
+
const path = require("node:path");
|
|
12
|
+
|
|
13
|
+
const ALLOWED_EXTENSIONS = [".md", ".markdown", ".png", ".jpg", ".jpeg", ".webp"];
|
|
14
|
+
const MAX_FILES = 10;
|
|
15
|
+
const MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
|
16
|
+
const MAX_TOTAL_SIZE_BYTES = 20 * 1024 * 1024;
|
|
17
|
+
const UPLOAD_PATH_PREFIX = "/api/cli/workitems/";
|
|
18
|
+
const UPLOAD_PATH_SUFFIX = "/requirement-documents";
|
|
19
|
+
|
|
20
|
+
const CONTENT_TYPES = {
|
|
21
|
+
".md": "text/markdown",
|
|
22
|
+
".markdown": "text/markdown",
|
|
23
|
+
".png": "image/png",
|
|
24
|
+
".jpg": "image/jpeg",
|
|
25
|
+
".jpeg": "image/jpeg",
|
|
26
|
+
".webp": "image/webp",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
class UploadError extends Error {
|
|
30
|
+
constructor(message, details = {}) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "UploadError";
|
|
33
|
+
Object.assign(this, details);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseUploadFlags(args) {
|
|
38
|
+
const FLAG_KEYS = {
|
|
39
|
+
"server-url": "serverUrl",
|
|
40
|
+
"workitem-id": "workitemId",
|
|
41
|
+
token: "token",
|
|
42
|
+
file: "file",
|
|
43
|
+
};
|
|
44
|
+
const options = { files: [], json: false };
|
|
45
|
+
for (let i = 0; i < args.length; i++) {
|
|
46
|
+
const arg = args[i];
|
|
47
|
+
if (arg === "--json") {
|
|
48
|
+
options.json = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (!arg.startsWith("--")) {
|
|
52
|
+
throw new UploadError(`unexpected argument: ${arg}`, { kind: "usage" });
|
|
53
|
+
}
|
|
54
|
+
const key = arg.slice(2);
|
|
55
|
+
if (!FLAG_KEYS[key]) {
|
|
56
|
+
throw new UploadError(`unknown option: ${arg}`, { kind: "usage" });
|
|
57
|
+
}
|
|
58
|
+
if (i + 1 >= args.length || args[i + 1].startsWith("--")) {
|
|
59
|
+
throw new UploadError(`--${key} requires a value`, { kind: "usage" });
|
|
60
|
+
}
|
|
61
|
+
const value = args[++i];
|
|
62
|
+
if (key === "file") {
|
|
63
|
+
options.files.push(value);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const optionKey = FLAG_KEYS[key];
|
|
67
|
+
if (options[optionKey] !== undefined) {
|
|
68
|
+
throw new UploadError(`--${key} may only be specified once`, { kind: "usage" });
|
|
69
|
+
}
|
|
70
|
+
options[optionKey] = value;
|
|
71
|
+
}
|
|
72
|
+
return options;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function validateServerUrl(value) {
|
|
76
|
+
if (!value) {
|
|
77
|
+
throw new UploadError("--server-url is required", { kind: "usage" });
|
|
78
|
+
}
|
|
79
|
+
let url;
|
|
80
|
+
try {
|
|
81
|
+
url = new URL(value);
|
|
82
|
+
} catch {
|
|
83
|
+
throw new UploadError(`--server-url must be an absolute http(s) URL: ${value}`, { kind: "usage" });
|
|
84
|
+
}
|
|
85
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
86
|
+
throw new UploadError(`--server-url must use http or https: ${value}`, { kind: "usage" });
|
|
87
|
+
}
|
|
88
|
+
if (url.username || url.password) {
|
|
89
|
+
throw new UploadError("--server-url must not embed credentials", { kind: "usage" });
|
|
90
|
+
}
|
|
91
|
+
if (url.search) {
|
|
92
|
+
throw new UploadError("--server-url must not contain a query string", { kind: "usage" });
|
|
93
|
+
}
|
|
94
|
+
if (url.hash) {
|
|
95
|
+
throw new UploadError("--server-url must not contain a fragment", { kind: "usage" });
|
|
96
|
+
}
|
|
97
|
+
return `${url.protocol}//${url.host}${url.pathname.replace(/\/+$/, "")}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function validateWorkitemId(value) {
|
|
101
|
+
if (value === undefined || value === null || !/^[1-9][0-9]*$/.test(String(value))) {
|
|
102
|
+
throw new UploadError(`--workitem-id must be a positive integer: ${value}`, { kind: "usage" });
|
|
103
|
+
}
|
|
104
|
+
return String(value);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function preflightFiles(filePaths) {
|
|
108
|
+
if (!filePaths.length) {
|
|
109
|
+
throw new UploadError("at least one --file is required", { kind: "usage" });
|
|
110
|
+
}
|
|
111
|
+
if (filePaths.length > MAX_FILES) {
|
|
112
|
+
throw new UploadError(`too many files: at most ${MAX_FILES} per upload`, { kind: "usage" });
|
|
113
|
+
}
|
|
114
|
+
let total = 0;
|
|
115
|
+
const prepared = [];
|
|
116
|
+
for (const filePath of filePaths) {
|
|
117
|
+
let stat;
|
|
118
|
+
try {
|
|
119
|
+
fs.accessSync(filePath, fs.constants.R_OK);
|
|
120
|
+
stat = fs.statSync(filePath);
|
|
121
|
+
} catch {
|
|
122
|
+
throw new UploadError(`file not found or not readable: ${filePath}`, { kind: "usage" });
|
|
123
|
+
}
|
|
124
|
+
if (!stat.isFile()) {
|
|
125
|
+
throw new UploadError(`not a regular file: ${filePath}`, { kind: "usage" });
|
|
126
|
+
}
|
|
127
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
128
|
+
if (!ALLOWED_EXTENSIONS.includes(extension)) {
|
|
129
|
+
throw new UploadError(
|
|
130
|
+
`unsupported file type: ${filePath} (allowed: ${ALLOWED_EXTENSIONS.join(", ")})`,
|
|
131
|
+
{ kind: "usage" },
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (stat.size > MAX_FILE_SIZE_BYTES) {
|
|
135
|
+
throw new UploadError(
|
|
136
|
+
`file too large: ${filePath} (${stat.size} bytes; limit is ${MAX_FILE_SIZE_BYTES})`,
|
|
137
|
+
{ kind: "usage" },
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
total += stat.size;
|
|
141
|
+
prepared.push({ filePath, filename: path.basename(filePath), size: stat.size });
|
|
142
|
+
}
|
|
143
|
+
if (total > MAX_TOTAL_SIZE_BYTES) {
|
|
144
|
+
throw new UploadError(
|
|
145
|
+
`total upload size too large: ${total} bytes (limit is ${MAX_TOTAL_SIZE_BYTES})`,
|
|
146
|
+
{ kind: "usage" },
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return prepared;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function writeChunk(request, chunk) {
|
|
153
|
+
return new Promise((resolve) => {
|
|
154
|
+
if (request.write(chunk)) {
|
|
155
|
+
resolve();
|
|
156
|
+
} else {
|
|
157
|
+
request.once("drain", resolve);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function writePart(request, boundary, file) {
|
|
163
|
+
const safeName = file.filename.replace(/["\r\n]/g, "_");
|
|
164
|
+
const contentType = CONTENT_TYPES[path.extname(file.filename).toLowerCase()] || "application/octet-stream";
|
|
165
|
+
await writeChunk(
|
|
166
|
+
request,
|
|
167
|
+
`--${boundary}\r\n` +
|
|
168
|
+
`Content-Disposition: form-data; name="files"; filename="${safeName}"\r\n` +
|
|
169
|
+
`Content-Type: ${contentType}\r\n\r\n`,
|
|
170
|
+
);
|
|
171
|
+
await new Promise((resolve, reject) => {
|
|
172
|
+
const stream = fs.createReadStream(file.filePath);
|
|
173
|
+
stream.on("error", (err) => {
|
|
174
|
+
err.filePath = file.filePath;
|
|
175
|
+
reject(err);
|
|
176
|
+
});
|
|
177
|
+
stream.on("data", (chunk) => {
|
|
178
|
+
if (!request.write(chunk)) {
|
|
179
|
+
stream.pause();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
request.on("drain", () => stream.resume());
|
|
183
|
+
stream.on("end", resolve);
|
|
184
|
+
});
|
|
185
|
+
await writeChunk(request, "\r\n");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function parseServerError(body) {
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(body);
|
|
191
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
192
|
+
} catch {
|
|
193
|
+
// non-JSON bodies are kept as-is by callers
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function uploadWorkitemDocuments({ serverUrl, workitemId, files, token }) {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
const boundary = `----autowonder${crypto.randomBytes(16).toString("hex")}`;
|
|
201
|
+
const url = new URL(`${serverUrl}${UPLOAD_PATH_PREFIX}${workitemId}${UPLOAD_PATH_SUFFIX}`);
|
|
202
|
+
const client = url.protocol === "https:" ? https : http;
|
|
203
|
+
let settled = false;
|
|
204
|
+
let sentAny = false;
|
|
205
|
+
|
|
206
|
+
const fail = (err) => {
|
|
207
|
+
if (!settled) {
|
|
208
|
+
settled = true;
|
|
209
|
+
reject(err);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
const finish = (value) => {
|
|
213
|
+
if (!settled) {
|
|
214
|
+
settled = true;
|
|
215
|
+
resolve(value);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const request = client.request(
|
|
220
|
+
url,
|
|
221
|
+
{
|
|
222
|
+
method: "POST",
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
|
225
|
+
Authorization: `Bearer ${token}`,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
(response) => {
|
|
229
|
+
if (response.statusCode >= 300 && response.statusCode < 400) {
|
|
230
|
+
request.destroy();
|
|
231
|
+
fail(new UploadError(
|
|
232
|
+
`server responded with a redirect (HTTP ${response.statusCode}); refusing to follow redirects`,
|
|
233
|
+
{ kind: "redirect", statusCode: response.statusCode },
|
|
234
|
+
));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
let body = "";
|
|
238
|
+
response.on("data", (chunk) => { body += chunk; });
|
|
239
|
+
response.on("end", () => finish({ statusCode: response.statusCode, body }));
|
|
240
|
+
response.on("error", (err) => fail(new UploadError(`response error: ${err.message}`, { kind: "network" })));
|
|
241
|
+
},
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
request.on("error", (err) => {
|
|
245
|
+
fail(new UploadError(
|
|
246
|
+
sentAny
|
|
247
|
+
? "connection lost after part of the upload was sent; the outcome is ambiguous. " +
|
|
248
|
+
"List the workitem's documents to see what arrived before retrying."
|
|
249
|
+
: `connection failed: ${err.message}`,
|
|
250
|
+
{ kind: "network" },
|
|
251
|
+
));
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
(async () => {
|
|
255
|
+
try {
|
|
256
|
+
for (const file of files) {
|
|
257
|
+
await writePart(request, boundary, file);
|
|
258
|
+
sentAny = true;
|
|
259
|
+
}
|
|
260
|
+
await writeChunk(request, `--${boundary}--\r\n`);
|
|
261
|
+
request.end();
|
|
262
|
+
} catch (err) {
|
|
263
|
+
request.destroy();
|
|
264
|
+
fail(new UploadError(
|
|
265
|
+
`failed reading ${err.filePath || "file"}: ${err.message}`,
|
|
266
|
+
{ kind: "read" },
|
|
267
|
+
));
|
|
268
|
+
}
|
|
269
|
+
})();
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = {
|
|
274
|
+
ALLOWED_EXTENSIONS,
|
|
275
|
+
MAX_FILES,
|
|
276
|
+
MAX_FILE_SIZE_BYTES,
|
|
277
|
+
MAX_TOTAL_SIZE_BYTES,
|
|
278
|
+
UploadError,
|
|
279
|
+
parseUploadFlags,
|
|
280
|
+
validateServerUrl,
|
|
281
|
+
validateWorkitemId,
|
|
282
|
+
preflightFiles,
|
|
283
|
+
parseServerError,
|
|
284
|
+
uploadWorkitemDocuments,
|
|
285
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autowonder",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.133",
|
|
4
4
|
"description": "AutoWonder local runtime — execute AI agent dispatch packages on your machine",
|
|
5
5
|
"bin": {
|
|
6
6
|
"autowonder": "bin/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"bin/",
|
|
10
|
+
"lib/",
|
|
10
11
|
"scripts/",
|
|
11
12
|
"vendor/"
|
|
12
13
|
],
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|