ysagc-agent 0.2.0 → 0.4.0

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.
Files changed (58) hide show
  1. package/README.md +36 -66
  2. package/dist/agent.d.ts +17 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +293 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/cli.d.ts +14 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +267 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +52 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +135 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/executor.d.ts +46 -0
  15. package/dist/executor.d.ts.map +1 -0
  16. package/dist/executor.js +397 -0
  17. package/dist/executor.js.map +1 -0
  18. package/dist/index.d.ts +19 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +34 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger.d.ts +37 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +121 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/path-utils.d.ts +44 -0
  27. package/dist/path-utils.d.ts.map +1 -0
  28. package/dist/path-utils.js +206 -0
  29. package/dist/path-utils.js.map +1 -0
  30. package/dist/protocol.d.ts +77 -0
  31. package/dist/protocol.d.ts.map +1 -0
  32. package/dist/protocol.js +94 -0
  33. package/dist/protocol.js.map +1 -0
  34. package/dist/security.d.ts +32 -0
  35. package/dist/security.d.ts.map +1 -0
  36. package/dist/security.js +228 -0
  37. package/dist/security.js.map +1 -0
  38. package/dist/tools.d.ts +36 -0
  39. package/dist/tools.d.ts.map +1 -0
  40. package/dist/tools.js +498 -0
  41. package/dist/tools.js.map +1 -0
  42. package/package.json +36 -21
  43. package/src/config.js +0 -94
  44. package/src/http-fs.js +0 -178
  45. package/src/index.js +0 -439
  46. package/src/logger.js +0 -84
  47. package/src/methods/dialog.js +0 -84
  48. package/src/methods/fs.js +0 -674
  49. package/src/methods/git.js +0 -391
  50. package/src/methods/project.js +0 -57
  51. package/src/methods/scaffold.js +0 -131
  52. package/src/methods/skill.js +0 -347
  53. package/src/methods/system.js +0 -82
  54. package/src/methods/terminal.js +0 -234
  55. package/src/origin.js +0 -38
  56. package/src/pairing.js +0 -168
  57. package/src/rpc.js +0 -62
  58. package/src/sandbox.js +0 -178
package/src/config.js DELETED
@@ -1,94 +0,0 @@
1
- /**
2
- * ysagc-agent 配置加载(agent.json)
3
- * 位置:~/.ysagc-agent/agent.json
4
- * 非法 JSON 自动备份恢复(agent.json.bak-<时间戳>)
5
- */
6
- 'use strict';
7
-
8
- const fs = require('fs');
9
- const os = require('os');
10
- const path = require('path');
11
-
12
- const CONFIG_DIR = process.env.YSAGC_AGENT_HOME || path.join(os.homedir(), '.ysagc-agent');
13
- const CONFIG_PATH = path.join(CONFIG_DIR, 'agent.json');
14
-
15
- const DEFAULTS = {
16
- port: 37890,
17
- allowedRoots: [],
18
- deviceTokenWhitelist: [],
19
- trashEnabled: true,
20
- trashRetentionDays: 7,
21
- autoApprovePaths: true,
22
- enableTerminal: true,
23
- terminalMaxSessions: 3,
24
- terminalIdleTimeoutMin: 30,
25
- backendUrl: 'https://chat.ysagc.top',
26
- logLevel: 'info',
27
- // 终端权限:ask(每次询问)/ allow(始终允许)/ deny(拒绝)
28
- terminalPermission: 'ask',
29
- // 协议版本(与后端握手比对)
30
- protocolVersion: 2,
31
- agentVersion: null,
32
- // ---- 免配对码直连(v0.2.0 起)----
33
- // 允许的浏览器 Origin 列表(精确匹配 origin,如 https://chat.ysagc.top)。
34
- // 为空时自动信任:backendUrl 对应的 origin + http://localhost / http://127.0.0.1 及其任意端口。
35
- // 该列表之外的 Origin 发起的 WebSocket / 本地 HTTP 请求将被拒绝(防恶意网页操控本机代理)。
36
- originWhitelist: [],
37
- };
38
-
39
- function ensureDir() {
40
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
41
- }
42
-
43
- function backupCorruptConfig() {
44
- if (!fs.existsSync(CONFIG_PATH)) return;
45
- const bak = path.join(CONFIG_DIR, `agent.json.bak-${Date.now()}`);
46
- try {
47
- fs.copyFileSync(CONFIG_PATH, bak);
48
- console.error(`[config] agent.json 解析失败,已备份到 ${bak},恢复默认配置`);
49
- } catch {
50
- /* 备份失败不阻塞 */
51
- }
52
- }
53
-
54
- function loadConfig() {
55
- ensureDir();
56
- let config = {};
57
- if (fs.existsSync(CONFIG_PATH)) {
58
- try {
59
- const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
60
- const parsed = JSON.parse(raw);
61
- if (parsed && typeof parsed === 'object') config = parsed;
62
- else backupCorruptConfig();
63
- } catch {
64
- backupCorruptConfig();
65
- }
66
- }
67
- const merged = { ...DEFAULTS, ...config };
68
- // 写入规范化后的配置(含缺失字段默认值),后续更新配置也走 saveConfig
69
- saveConfig(merged, { silent: true });
70
- return merged;
71
- }
72
-
73
- function saveConfig(patch, opts = {}) {
74
- ensureDir();
75
- const current = loadConfigRaw();
76
- const next = { ...DEFAULTS, ...current, ...patch };
77
- const tmp = CONFIG_PATH + '.tmp';
78
- fs.writeFileSync(tmp, JSON.stringify(next, null, 2));
79
- fs.renameSync(tmp, CONFIG_PATH); // 原子替换,防损坏
80
- if (!opts.silent) console.log(`[config] 配置已保存: ${CONFIG_PATH}`);
81
- return next;
82
- }
83
-
84
- function loadConfigRaw() {
85
- try {
86
- if (!fs.existsSync(CONFIG_PATH)) return {};
87
- const parsed = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
88
- return parsed && typeof parsed === 'object' ? parsed : {};
89
- } catch {
90
- return {};
91
- }
92
- }
93
-
94
- module.exports = { CONFIG_DIR, CONFIG_PATH, DEFAULTS, loadConfig, saveConfig };
package/src/http-fs.js DELETED
@@ -1,178 +0,0 @@
1
- /**
2
- * Agent 本地 HTTP:上传/下载(T-M1-05)
3
- * - POST /api/fs/upload?token=&path=&overwrite=1 分片上传(X-Offset / X-Total 头)
4
- * - GET /api/fs/download?token=&path= 单文件下载(流式)
5
- * - GET /api/fs/zip?token=&paths=a,b,c zip 打包下载(yazl 流式)
6
- * - 鉴权:deviceToken sha256 与白名单比对;路径经沙箱校验(allowlist)
7
- * - 分片:临时文件 append,达到 X-Total 后原子 rename;覆盖需 overwrite=1(前端先 409 确认)
8
- */
9
- 'use strict';
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const crypto = require('crypto');
14
- const url = require('url');
15
-
16
- const { isOriginTrusted } = require('./origin');
17
-
18
- const MAX_UPLOAD_SIZE = 512 * 1024 * 1024; // 512MB
19
- const MAX_ZIP_FILES = 2000;
20
-
21
- function sha256(s) {
22
- return crypto.createHash('sha256').update(String(s)).digest('hex');
23
- }
24
-
25
- function createHttpFsHandlers(ctxRef) {
26
- const sandbox = () => ctxRef().sandbox;
27
- const cfg = () => ctxRef().config;
28
-
29
- function checkToken(token, req) {
30
- // 免配对码直连(v0.2.0):Origin 受信任的浏览器请求直接放行
31
- if (req) {
32
- const origin = req.headers && req.headers.origin;
33
- if (origin && isOriginTrusted(origin, cfg())) return true;
34
- }
35
- if (!token) return false;
36
- return (cfg().deviceTokenWhitelist || []).includes(sha256(token));
37
- }
38
-
39
- function sendJson(res, status, obj) {
40
- res.writeHead(status, { 'Content-Type': 'application/json' });
41
- res.end(JSON.stringify(obj));
42
- }
43
-
44
- /** 上传:分片追加到临时文件,最后一片 rename 落盘 */
45
- function handleUpload(req, res, query) {
46
- const token = query.token;
47
- if (!checkToken(token, req)) return sendJson(res, 401, { code: 'E_AUTH_FAILED', msg: '未授权' });
48
- const relPath = String(query.path || '');
49
- if (!relPath) return sendJson(res, 400, { code: 'E_VALIDATION', msg: '缺少 path' });
50
- let r;
51
- try {
52
- r = sandbox().resolve(relPath, '');
53
- } catch (e) {
54
- return sendJson(res, 403, { code: 'E_PATH_ESCAPE', msg: e.message || '路径越界' });
55
- }
56
- if (r.rel === '' || r.rel === '.') return sendJson(res, 400, { code: 'E_VALIDATION', msg: '不能上传到项目根' });
57
-
58
- const offset = Math.max(0, Number(req.headers['x-offset']) || 0);
59
- const total = Math.max(0, Number(req.headers['x-total']) || 0);
60
- if (total > MAX_UPLOAD_SIZE) return sendJson(res, 413, { code: 'E_FILE_TOO_LARGE', msg: '文件超过 512MB 限制' });
61
- const overwrite = String(query.overwrite || '') === '1';
62
-
63
- // 目标已存在 → 409 需确认
64
- if (offset === 0 && !overwrite && fs.existsSync(r.abs)) {
65
- return sendJson(res, 409, { code: 'E_PATH_EXISTS', msg: '目标文件已存在,请确认覆盖', needConfirm: true });
66
- }
67
-
68
- const tmpPath = path.join(r.root, `.upload-tmp-${sha256(relPath).slice(0, 16)}`);
69
- try {
70
- // 首片:若需覆盖且临时文件残留则清理
71
- if (offset === 0 && fs.existsSync(tmpPath)) fs.rmSync(tmpPath);
72
- const fd = fs.openSync(tmpPath, offset === 0 ? 'w' : 'a');
73
- const chunks = [];
74
- req.on('data', (c) => chunks.push(c));
75
- req.on('end', () => {
76
- const buf = Buffer.concat(chunks);
77
- fs.writeSync(fd, buf, 0, buf.length, offset);
78
- fs.closeSync(fd);
79
- const curSize = offset + buf.length;
80
- if (total > 0 && curSize >= total) {
81
- // 完成:原子 rename
82
- fs.renameSync(tmpPath, r.abs);
83
- return sendJson(res, 200, { code: 0, data: { ok: true, size: curSize, relPath: r.rel }, msg: 'success' });
84
- }
85
- return sendJson(res, 200, { code: 0, data: { ok: true, received: curSize, total }, msg: 'chunk' });
86
- });
87
- req.on('error', () => {
88
- try { fs.closeSync(fd); } catch { /* ignore */ }
89
- sendJson(res, 500, { code: 'E_INTERNAL', msg: '上传中断' });
90
- });
91
- } catch (e) {
92
- return sendJson(res, 500, { code: 'E_INTERNAL', msg: e.message });
93
- }
94
- }
95
-
96
- /** 单文件下载(流式) */
97
- function handleDownload(req, res, query) {
98
- const token = query.token;
99
- if (!checkToken(token, req)) return sendJson(res, 401, { code: 'E_AUTH_FAILED', msg: '未授权' });
100
- const relPath = String(query.path || '');
101
- let r;
102
- try {
103
- r = sandbox().resolve(relPath, '');
104
- } catch (e) {
105
- return sendJson(res, 403, { code: 'E_PATH_ESCAPE', msg: e.message || '路径越界' });
106
- }
107
- if (!fs.existsSync(r.abs) || !fs.statSync(r.abs).isFile()) {
108
- return sendJson(res, 404, { code: 'E_PATH_NOT_FOUND', msg: '文件不存在' });
109
- }
110
- res.writeHead(200, {
111
- 'Content-Type': 'application/octet-stream',
112
- 'Content-Disposition': `attachment; filename="${encodeURIComponent(path.basename(r.abs))}"`,
113
- 'Content-Length': fs.statSync(r.abs).size,
114
- });
115
- fs.createReadStream(r.abs).pipe(res);
116
- }
117
-
118
- /** zip 打包下载(yazl 流式) */
119
- function handleZip(req, res, query) {
120
- const token = query.token;
121
- if (!checkToken(token, req)) return sendJson(res, 401, { code: 'E_AUTH_FAILED', msg: '未授权' });
122
- const paths = String(query.paths || '')
123
- .split(',')
124
- .map((p) => p.trim())
125
- .filter(Boolean);
126
- if (paths.length === 0 || paths.length > MAX_ZIP_FILES) {
127
- return sendJson(res, 400, { code: 'E_VALIDATION', msg: 'paths 数量需在 1-2000 之间' });
128
- }
129
- const yazl = require('yazl');
130
- const zipfile = new yazl.ZipFile();
131
- let added = 0;
132
- for (const rel of paths) {
133
- let r;
134
- try {
135
- r = sandbox().resolve(rel, '');
136
- } catch {
137
- return sendJson(res, 403, { code: 'E_PATH_ESCAPE', msg: '路径越界: ' + rel });
138
- }
139
- if (!fs.existsSync(r.abs)) continue;
140
- const st = fs.statSync(r.abs);
141
- if (st.isDirectory()) {
142
- // 递归目录
143
- const walk = (dir, relBase) => {
144
- let entries;
145
- try {
146
- entries = fs.readdirSync(dir, { withFileTypes: true });
147
- } catch {
148
- return;
149
- }
150
- for (const e of entries) {
151
- const abs = path.join(dir, e.name);
152
- const relP = relBase ? `${relBase}/${e.name}` : e.name;
153
- if (e.isDirectory()) walk(abs, relP);
154
- else if (e.isFile()) {
155
- zipfile.addFile(abs, relP);
156
- added++;
157
- }
158
- }
159
- };
160
- walk(r.abs, path.basename(r.abs));
161
- } else if (st.isFile()) {
162
- zipfile.addFile(r.abs, path.basename(r.abs));
163
- added++;
164
- }
165
- }
166
- if (added === 0) return sendJson(res, 404, { code: 'E_PATH_NOT_FOUND', msg: '没有可打包的文件' });
167
- res.writeHead(200, {
168
- 'Content-Type': 'application/zip',
169
- 'Content-Disposition': `attachment; filename="workspace-${Date.now()}.zip"`,
170
- });
171
- zipfile.outputStream.pipe(res);
172
- zipfile.end();
173
- }
174
-
175
- return { handleUpload, handleDownload, handleZip };
176
- }
177
-
178
- module.exports = { createHttpFsHandlers };