aws-runtime-bridge 1.7.25 → 1.7.26
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/routes/file-browser.d.ts +33 -0
- package/dist/routes/file-browser.d.ts.map +1 -1
- package/dist/routes/file-browser.js +153 -0
- package/dist/routes/file-browser.test.js +37 -1
- package/dist/routes/git.d.ts +20 -0
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +78 -15
- package/dist/routes/git.test.js +28 -4
- package/dist/routes/properties.test.js +2 -2
- package/dist/routes/yml.js +6 -6
- package/dist/routes/yml.test.js +3 -3
- package/dist/utils/path-utils.js +1 -1
- package/dist/utils/yaml-utils.d.ts +4 -4
- package/dist/utils/yaml-utils.js +4 -4
- package/dist/utils/yaml-utils.test.js +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +2 -7
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +4 -15
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +50 -6
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +21 -3
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js +68 -20
- package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.js +50 -44
- package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,32 @@
|
|
|
6
6
|
import multer from 'multer';
|
|
7
7
|
export declare const fileBrowserRouter: import("express-serve-static-core").Router;
|
|
8
8
|
export declare const WORKSPACE_UPLOAD_FILE_LIMIT = 2000;
|
|
9
|
+
interface FileBrowserItem {
|
|
10
|
+
name: string;
|
|
11
|
+
path: string;
|
|
12
|
+
isDirectory: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface FileBrowserResponse {
|
|
15
|
+
isWindows: boolean;
|
|
16
|
+
currentPath: string;
|
|
17
|
+
items: FileBrowserItem[];
|
|
18
|
+
}
|
|
19
|
+
interface DirectoryTreeItem extends FileBrowserItem {
|
|
20
|
+
children?: DirectoryTreeItem[];
|
|
21
|
+
truncated?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface DirectoryTreeResponse extends FileBrowserResponse {
|
|
24
|
+
maxDepth: number;
|
|
25
|
+
maxItemsPerDirectory: number;
|
|
26
|
+
tree: DirectoryTreeItem[];
|
|
27
|
+
}
|
|
28
|
+
interface GitIgnoreRule {
|
|
29
|
+
pattern: string;
|
|
30
|
+
negated: boolean;
|
|
31
|
+
directoryOnly: boolean;
|
|
32
|
+
anchored: boolean;
|
|
33
|
+
hasSlash: boolean;
|
|
34
|
+
}
|
|
9
35
|
export declare function createWorkspaceUploadLimitResponse(error: multer.MulterError): {
|
|
10
36
|
status: number;
|
|
11
37
|
body: {
|
|
@@ -14,4 +40,11 @@ export declare function createWorkspaceUploadLimitResponse(error: multer.MulterE
|
|
|
14
40
|
};
|
|
15
41
|
};
|
|
16
42
|
export declare function parseWorkspaceUploadRelativePaths(body: Record<string, unknown>): string[];
|
|
43
|
+
/**
|
|
44
|
+
* 主流程:从选中的工作目录生成受限目录树,最多 10 层且每个目录只返回前 20 个子目录。
|
|
45
|
+
*/
|
|
46
|
+
export declare function listHostDirectoryTree(requestPath?: string): Promise<DirectoryTreeResponse>;
|
|
47
|
+
export declare function parseGitIgnoreRules(content: string): GitIgnoreRule[];
|
|
48
|
+
export declare function isIgnoredByGitIgnore(relativePath: string, isDirectory: boolean, rules: GitIgnoreRule[]): boolean;
|
|
49
|
+
export {};
|
|
17
50
|
//# sourceMappingURL=file-browser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-browser.d.ts","sourceRoot":"","sources":["../../src/routes/file-browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAuB5B,eAAO,MAAM,iBAAiB,4CAAW,CAAC;AAE1C,eAAO,MAAM,2BAA2B,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"file-browser.d.ts","sourceRoot":"","sources":["../../src/routes/file-browser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAuB5B,eAAO,MAAM,iBAAiB,4CAAW,CAAC;AAE1C,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAUhD,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,UAAU,mBAAmB;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAKD,UAAU,iBAAkB,SAAQ,eAAe;IACjD,QAAQ,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,qBAAsB,SAAQ,mBAAmB;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,IAAI,EAAE,iBAAiB,EAAE,CAAC;CAC3B;AAED,UAAU,aAAa;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAiBD,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAUvI;AAED,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAgBzF;AAyFD;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,SAAK,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAkB5F;AAoCD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAuBpE;AAWD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAWhH"}
|
|
@@ -23,6 +23,8 @@ const upload = multer({
|
|
|
23
23
|
fileSize: 512 * 1024 * 1024
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
|
+
const DIRECTORY_TREE_MAX_DEPTH = 10;
|
|
27
|
+
const DIRECTORY_TREE_MAX_ITEMS_PER_DIRECTORY = 20;
|
|
26
28
|
function toBoolean(value) {
|
|
27
29
|
return value === true || value === 'true' || value === '1';
|
|
28
30
|
}
|
|
@@ -141,6 +143,147 @@ async function listHostDirectories(requestPath = '') {
|
|
|
141
143
|
items
|
|
142
144
|
};
|
|
143
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* 主流程:从选中的工作目录生成受限目录树,最多 10 层且每个目录只返回前 20 个子目录。
|
|
148
|
+
*/
|
|
149
|
+
export async function listHostDirectoryTree(requestPath = '') {
|
|
150
|
+
const root = await listHostDirectories(requestPath);
|
|
151
|
+
const ignoreRules = root.currentPath ? await loadGitIgnoreRules(root.currentPath) : [];
|
|
152
|
+
if (!root.currentPath) {
|
|
153
|
+
return {
|
|
154
|
+
...root,
|
|
155
|
+
maxDepth: DIRECTORY_TREE_MAX_DEPTH,
|
|
156
|
+
maxItemsPerDirectory: DIRECTORY_TREE_MAX_ITEMS_PER_DIRECTORY,
|
|
157
|
+
tree: root.items.slice(0, DIRECTORY_TREE_MAX_ITEMS_PER_DIRECTORY),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
...root,
|
|
162
|
+
maxDepth: DIRECTORY_TREE_MAX_DEPTH,
|
|
163
|
+
maxItemsPerDirectory: DIRECTORY_TREE_MAX_ITEMS_PER_DIRECTORY,
|
|
164
|
+
tree: await buildDirectoryTree(root.currentPath, root.currentPath, 1, ignoreRules),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function buildDirectoryTree(rootPath, directoryPath, depth, ignoreRules) {
|
|
168
|
+
if (depth > DIRECTORY_TREE_MAX_DEPTH) {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
const entries = await fs.readdir(directoryPath, { withFileTypes: true });
|
|
172
|
+
const directories = entries
|
|
173
|
+
.filter((entry) => entry.isDirectory())
|
|
174
|
+
.filter((entry) => !isIgnoredDirectory(rootPath, path.join(directoryPath, entry.name), ignoreRules))
|
|
175
|
+
.sort((left, right) => left.name.localeCompare(right.name))
|
|
176
|
+
.slice(0, DIRECTORY_TREE_MAX_ITEMS_PER_DIRECTORY);
|
|
177
|
+
const nodes = [];
|
|
178
|
+
for (const entry of directories) {
|
|
179
|
+
const childPath = path.join(directoryPath, entry.name);
|
|
180
|
+
const node = {
|
|
181
|
+
name: entry.name,
|
|
182
|
+
path: childPath.replace(/\\/g, '/'),
|
|
183
|
+
isDirectory: true,
|
|
184
|
+
};
|
|
185
|
+
if (depth < DIRECTORY_TREE_MAX_DEPTH) {
|
|
186
|
+
try {
|
|
187
|
+
node.children = await buildDirectoryTree(rootPath, childPath, depth + 1, ignoreRules);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
node.children = [];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
node.truncated = true;
|
|
195
|
+
}
|
|
196
|
+
nodes.push(node);
|
|
197
|
+
}
|
|
198
|
+
return nodes;
|
|
199
|
+
}
|
|
200
|
+
export function parseGitIgnoreRules(content) {
|
|
201
|
+
return String(content || '')
|
|
202
|
+
.split(/\r?\n/)
|
|
203
|
+
.map((line) => line.trim())
|
|
204
|
+
.filter((line) => line && !line.startsWith('#'))
|
|
205
|
+
.map((line) => {
|
|
206
|
+
const negated = line.startsWith('!');
|
|
207
|
+
const rawPattern = negated ? line.slice(1).trim() : line;
|
|
208
|
+
const anchored = rawPattern.startsWith('/');
|
|
209
|
+
const directoryOnly = rawPattern.endsWith('/');
|
|
210
|
+
const pattern = rawPattern
|
|
211
|
+
.replace(/^\/+/, '')
|
|
212
|
+
.replace(/\/+$/, '')
|
|
213
|
+
.replace(/\\/g, '/');
|
|
214
|
+
return {
|
|
215
|
+
pattern,
|
|
216
|
+
negated,
|
|
217
|
+
directoryOnly,
|
|
218
|
+
anchored,
|
|
219
|
+
hasSlash: pattern.includes('/'),
|
|
220
|
+
};
|
|
221
|
+
})
|
|
222
|
+
.filter((rule) => rule.pattern.length > 0);
|
|
223
|
+
}
|
|
224
|
+
async function loadGitIgnoreRules(rootPath) {
|
|
225
|
+
try {
|
|
226
|
+
const content = await fs.readFile(path.join(rootPath, '.gitignore'), 'utf8');
|
|
227
|
+
return parseGitIgnoreRules(content);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
export function isIgnoredByGitIgnore(relativePath, isDirectory, rules) {
|
|
234
|
+
const normalizedPath = normalizeRelativePath(relativePath);
|
|
235
|
+
if (!normalizedPath)
|
|
236
|
+
return false;
|
|
237
|
+
let ignored = false;
|
|
238
|
+
for (const rule of rules) {
|
|
239
|
+
if (rule.directoryOnly && !isDirectory)
|
|
240
|
+
continue;
|
|
241
|
+
if (matchesGitIgnoreRule(normalizedPath, rule)) {
|
|
242
|
+
ignored = !rule.negated;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return ignored;
|
|
246
|
+
}
|
|
247
|
+
function isIgnoredDirectory(rootPath, directoryPath, ignoreRules) {
|
|
248
|
+
if (ignoreRules.length === 0)
|
|
249
|
+
return false;
|
|
250
|
+
const relativePath = path.relative(rootPath, directoryPath).replace(/\\/g, '/');
|
|
251
|
+
return isIgnoredByGitIgnore(relativePath, true, ignoreRules);
|
|
252
|
+
}
|
|
253
|
+
function matchesGitIgnoreRule(relativePath, rule) {
|
|
254
|
+
if (!rule.hasSlash && !rule.anchored) {
|
|
255
|
+
return relativePath.split('/').some((segment) => matchesGlobSegment(segment, rule.pattern));
|
|
256
|
+
}
|
|
257
|
+
const pattern = rule.pattern;
|
|
258
|
+
if (rule.anchored) {
|
|
259
|
+
return matchesGlobPath(relativePath, pattern) || relativePath.startsWith(pattern + '/');
|
|
260
|
+
}
|
|
261
|
+
return matchesGlobPath(relativePath, pattern)
|
|
262
|
+
|| relativePath.endsWith('/' + pattern)
|
|
263
|
+
|| relativePath.includes('/' + pattern + '/');
|
|
264
|
+
}
|
|
265
|
+
function matchesGlobPath(relativePath, pattern) {
|
|
266
|
+
const regex = new RegExp('^' + globToRegex(pattern) + '(?:/.*)?$');
|
|
267
|
+
return regex.test(relativePath);
|
|
268
|
+
}
|
|
269
|
+
function matchesGlobSegment(segment, pattern) {
|
|
270
|
+
const regex = new RegExp('^' + globToRegex(pattern) + '$');
|
|
271
|
+
return regex.test(segment);
|
|
272
|
+
}
|
|
273
|
+
function globToRegex(pattern) {
|
|
274
|
+
return pattern
|
|
275
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
276
|
+
.replace(/\*\*/g, '.*')
|
|
277
|
+
.replace(/\*/g, '[^/]*')
|
|
278
|
+
.replace(/\?/g, '[^/]');
|
|
279
|
+
}
|
|
280
|
+
function normalizeRelativePath(relativePath) {
|
|
281
|
+
return String(relativePath || '')
|
|
282
|
+
.replace(/\\/g, '/')
|
|
283
|
+
.replace(/^\.\//, '')
|
|
284
|
+
.replace(/^\/+/, '')
|
|
285
|
+
.replace(/\/+$/, '');
|
|
286
|
+
}
|
|
144
287
|
async function resolveHostDirectoryPath(targetPath) {
|
|
145
288
|
const normalizedPath = path.normalize(String(targetPath || ''));
|
|
146
289
|
const stat = await fs.stat(normalizedPath);
|
|
@@ -218,6 +361,16 @@ fileBrowserRouter.post('/directory-picker/list', validateToken, async (req, res)
|
|
|
218
361
|
res.status(400).json({ error: err.message });
|
|
219
362
|
}
|
|
220
363
|
});
|
|
364
|
+
fileBrowserRouter.post('/directory-picker/tree', validateToken, async (req, res) => {
|
|
365
|
+
try {
|
|
366
|
+
const { path: requestPath = '' } = req.body || {};
|
|
367
|
+
res.json(await listHostDirectoryTree(String(requestPath || '')));
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
const err = error;
|
|
371
|
+
res.status(400).json({ error: err.message });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
221
374
|
/**
|
|
222
375
|
* 列出工作区目录内容
|
|
223
376
|
* POST /api/file-browser/workspace/list
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
* File Browser 路由单元测试
|
|
3
3
|
*/
|
|
4
4
|
import { describe, it, expect } from 'vitest';
|
|
5
|
+
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
5
8
|
import multer from 'multer';
|
|
6
|
-
import { createWorkspaceUploadLimitResponse, parseWorkspaceUploadRelativePaths, WORKSPACE_UPLOAD_FILE_LIMIT } from './file-browser.js';
|
|
9
|
+
import { createWorkspaceUploadLimitResponse, isIgnoredByGitIgnore, listHostDirectoryTree, parseGitIgnoreRules, parseWorkspaceUploadRelativePaths, WORKSPACE_UPLOAD_FILE_LIMIT } from './file-browser.js';
|
|
7
10
|
describe('file-browser route validation', () => {
|
|
8
11
|
it('returns root drives on Windows when path is empty', () => {
|
|
9
12
|
const getRootForWindows = () => {
|
|
@@ -117,3 +120,36 @@ describe('file-browser response building', () => {
|
|
|
117
120
|
expect(relativePaths).toEqual(['a/b/c.txt', 'a/d/e.txt']);
|
|
118
121
|
});
|
|
119
122
|
});
|
|
123
|
+
describe('directory tree gitignore filtering', () => {
|
|
124
|
+
it('matches ignored directories from .gitignore rules', () => {
|
|
125
|
+
const rules = parseGitIgnoreRules(`
|
|
126
|
+
node_modules/
|
|
127
|
+
/dist/
|
|
128
|
+
logs*
|
|
129
|
+
!logs-keep/
|
|
130
|
+
`);
|
|
131
|
+
expect(isIgnoredByGitIgnore('node_modules', true, rules)).toBe(true);
|
|
132
|
+
expect(isIgnoredByGitIgnore('packages/app/node_modules', true, rules)).toBe(true);
|
|
133
|
+
expect(isIgnoredByGitIgnore('dist', true, rules)).toBe(true);
|
|
134
|
+
expect(isIgnoredByGitIgnore('packages/app/dist', true, rules)).toBe(false);
|
|
135
|
+
expect(isIgnoredByGitIgnore('logs-temp', true, rules)).toBe(true);
|
|
136
|
+
expect(isIgnoredByGitIgnore('logs-keep', true, rules)).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
it('filters actual directory tree entries using root .gitignore', async () => {
|
|
139
|
+
const root = await mkdtemp(path.join(tmpdir(), 'aws-tree-ignore-'));
|
|
140
|
+
try {
|
|
141
|
+
await mkdir(path.join(root, 'src'));
|
|
142
|
+
await mkdir(path.join(root, 'node_modules'));
|
|
143
|
+
await mkdir(path.join(root, 'dist'));
|
|
144
|
+
await writeFile(path.join(root, '.gitignore'), 'node_modules/\n/dist/\n');
|
|
145
|
+
const result = await listHostDirectoryTree(root);
|
|
146
|
+
const names = result.tree.map((item) => item.name);
|
|
147
|
+
expect(names).toContain('src');
|
|
148
|
+
expect(names).not.toContain('node_modules');
|
|
149
|
+
expect(names).not.toContain('dist');
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
await rm(root, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
});
|
package/dist/routes/git.d.ts
CHANGED
|
@@ -11,5 +11,25 @@ type GitDiffFileStatus = 'modified' | 'added' | 'deleted' | 'renamed';
|
|
|
11
11
|
* 普通 modified 且文本增删均为 0 通常是 filemode/元数据噪声,避免显示为“无差异内容”。
|
|
12
12
|
*/
|
|
13
13
|
export declare function shouldIncludeGitDiffSummaryFile(status: GitDiffFileStatus, additions: number, deletions: number, isBinaryDiff: boolean): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* 判断 git stash 是否因仓库尚无初始提交而失败。
|
|
16
|
+
* 主流程:兼容 Git 不同版本输出,将底层英文错误归一成可面向用户的诊断。
|
|
17
|
+
*/
|
|
18
|
+
export declare function isGitStashMissingInitialCommitError(output: string): boolean;
|
|
19
|
+
export declare function createMissingInitialCommitSnapshotError(): string;
|
|
20
|
+
/**
|
|
21
|
+
* 判断 git stash push 是否明确表示没有本地变更。
|
|
22
|
+
* 主流程:兼容不同 Git 版本把 no-change 信息输出到 stdout 或 stderr,且可能返回 0 或非 0。
|
|
23
|
+
*/
|
|
24
|
+
export declare function isGitStashNoChangesOutput(output: string): boolean;
|
|
25
|
+
interface LatestGitStash {
|
|
26
|
+
ref: string;
|
|
27
|
+
message: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 从 git stash list -n 1 输出解析最近一次 stash。
|
|
31
|
+
* 主流程:提取 stash 引用并保留后续描述,供无变更快照复用最近快照引用。
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseLatestGitStash(output: string): LatestGitStash | null;
|
|
14
34
|
export {};
|
|
15
35
|
//# sourceMappingURL=git.d.ts.map
|
package/dist/routes/git.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAWlC,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AASD,KAAK,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,GACpB,OAAO,CAMT"}
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAWlC,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AASD,KAAK,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,GACpB,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAK3E;AAED,wBAAgB,uCAAuC,IAAI,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAKjE;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAWzE"}
|
package/dist/routes/git.js
CHANGED
|
@@ -35,6 +35,45 @@ export function shouldIncludeGitDiffSummaryFile(status, additions, deletions, is
|
|
|
35
35
|
}
|
|
36
36
|
return isBinaryDiff || additions > 0 || deletions > 0;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* 判断 git stash 是否因仓库尚无初始提交而失败。
|
|
40
|
+
* 主流程:兼容 Git 不同版本输出,将底层英文错误归一成可面向用户的诊断。
|
|
41
|
+
*/
|
|
42
|
+
export function isGitStashMissingInitialCommitError(output) {
|
|
43
|
+
const normalized = String(output || '').toLowerCase();
|
|
44
|
+
return normalized.includes('you do not have the initial commit yet')
|
|
45
|
+
|| (normalized.includes('bad revision') && normalized.includes('head'))
|
|
46
|
+
|| (normalized.includes('ambiguous argument') && normalized.includes('head'));
|
|
47
|
+
}
|
|
48
|
+
export function createMissingInitialCommitSnapshotError() {
|
|
49
|
+
return '当前 Git 仓库还没有初始提交,无法创建快照。请先提交一次初始提交后再创建快照。';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 判断 git stash push 是否明确表示没有本地变更。
|
|
53
|
+
* 主流程:兼容不同 Git 版本把 no-change 信息输出到 stdout 或 stderr,且可能返回 0 或非 0。
|
|
54
|
+
*/
|
|
55
|
+
export function isGitStashNoChangesOutput(output) {
|
|
56
|
+
const normalized = String(output || '').toLowerCase();
|
|
57
|
+
return normalized.includes('no local changes to save')
|
|
58
|
+
|| normalized.includes('no changes to save')
|
|
59
|
+
|| normalized.includes('没有本地变更需要保存');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 从 git stash list -n 1 输出解析最近一次 stash。
|
|
63
|
+
* 主流程:提取 stash 引用并保留后续描述,供无变更快照复用最近快照引用。
|
|
64
|
+
*/
|
|
65
|
+
export function parseLatestGitStash(output) {
|
|
66
|
+
const firstLine = String(output || '').split('\n').find(line => line.trim());
|
|
67
|
+
if (!firstLine)
|
|
68
|
+
return null;
|
|
69
|
+
const match = firstLine.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);
|
|
70
|
+
if (!match)
|
|
71
|
+
return null;
|
|
72
|
+
return {
|
|
73
|
+
ref: match[1],
|
|
74
|
+
message: match[2]?.trim() || match[1],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
38
77
|
/**
|
|
39
78
|
* 执行 git 命令并返回输出
|
|
40
79
|
*/
|
|
@@ -209,15 +248,24 @@ gitRouter.post('/git/stash/create', validateToken, async (req, res) => {
|
|
|
209
248
|
? `快照: ${String(message).trim()}`
|
|
210
249
|
: defaultMessage;
|
|
211
250
|
const result = await execGitCommand(normalizedPath, ['stash', 'push', '-m', stashMessage]);
|
|
251
|
+
if (isGitStashNoChangesOutput(`${result.stdout}\n${result.stderr}`)) {
|
|
252
|
+
const latestResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1']);
|
|
253
|
+
const latestStash = latestResult.exitCode === 0 ? parseLatestGitStash(latestResult.stdout) : null;
|
|
254
|
+
res.json({
|
|
255
|
+
ok: true,
|
|
256
|
+
stashRef: latestStash?.ref ?? null,
|
|
257
|
+
message: latestStash
|
|
258
|
+
? `没有本地变更需要保存,已复用最近快照 ${latestStash.ref}`
|
|
259
|
+
: '没有本地变更需要保存',
|
|
260
|
+
noChanges: true,
|
|
261
|
+
reusedLatestStash: Boolean(latestStash),
|
|
262
|
+
workspacePath: normalizedPath
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
212
266
|
if (result.exitCode !== 0) {
|
|
213
|
-
if (result.stderr
|
|
214
|
-
res.json({
|
|
215
|
-
ok: true,
|
|
216
|
-
stashRef: null,
|
|
217
|
-
message: '没有本地变更需要保存',
|
|
218
|
-
noChanges: true,
|
|
219
|
-
workspacePath: normalizedPath
|
|
220
|
-
});
|
|
267
|
+
if (isGitStashMissingInitialCommitError(`${result.stdout}\n${result.stderr}`)) {
|
|
268
|
+
res.status(400).json({ error: createMissingInitialCommitSnapshotError() });
|
|
221
269
|
return;
|
|
222
270
|
}
|
|
223
271
|
res.status(400).json({ error: result.stderr || 'git stash push failed' });
|
|
@@ -292,10 +340,10 @@ gitRouter.post('/git/stash/list', validateToken, async (req, res) => {
|
|
|
292
340
|
}
|
|
293
341
|
});
|
|
294
342
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
343
|
+
* 应用或弹出 git stash 快照。
|
|
344
|
+
* 主流程:恢复前先清理工作区未提交变更,再执行 apply/pop,并统一处理冲突和引用不存在错误。
|
|
297
345
|
*/
|
|
298
|
-
|
|
346
|
+
async function restoreGitStash(req, res, mode) {
|
|
299
347
|
const { workspacePath, ref } = req.body || {};
|
|
300
348
|
if (!workspacePath || !String(workspacePath).trim()) {
|
|
301
349
|
res.status(400).json({ error: 'workspacePath is required' });
|
|
@@ -305,7 +353,7 @@ gitRouter.post('/git/stash/pop', validateToken, async (req, res) => {
|
|
|
305
353
|
const normalizedPath = path.resolve(String(workspacePath).trim());
|
|
306
354
|
const stashRef = ref && String(ref).trim() ? String(ref).trim() : 'stash@{0}';
|
|
307
355
|
await execGitCommand(normalizedPath, ['checkout', '--', '.']);
|
|
308
|
-
const result = await execGitCommand(normalizedPath, ['stash',
|
|
356
|
+
const result = await execGitCommand(normalizedPath, ['stash', mode, stashRef]);
|
|
309
357
|
if (result.exitCode !== 0) {
|
|
310
358
|
if (result.stdout.includes('CONFLICT') || result.stderr.includes('CONFLICT')) {
|
|
311
359
|
res.json({
|
|
@@ -321,20 +369,35 @@ gitRouter.post('/git/stash/pop', validateToken, async (req, res) => {
|
|
|
321
369
|
res.status(404).json({ error: `快照 ${stashRef} 不存在` });
|
|
322
370
|
return;
|
|
323
371
|
}
|
|
324
|
-
res.status(400).json({ error: result.stderr ||
|
|
372
|
+
res.status(400).json({ error: result.stderr || `git stash ${mode} failed` });
|
|
325
373
|
return;
|
|
326
374
|
}
|
|
327
375
|
res.json({
|
|
328
376
|
ok: true,
|
|
329
377
|
stashRef,
|
|
330
|
-
message: `已恢复 ${stashRef}`,
|
|
378
|
+
message: mode === 'apply' ? `已应用 ${stashRef}` : `已恢复 ${stashRef}`,
|
|
379
|
+
preservedStash: mode === 'apply',
|
|
331
380
|
workspacePath: normalizedPath
|
|
332
381
|
});
|
|
333
382
|
}
|
|
334
383
|
catch (error) {
|
|
335
384
|
const err = error;
|
|
336
|
-
res.status(500).json({ error: err.message ||
|
|
385
|
+
res.status(500).json({ error: err.message || `${mode} stash failed` });
|
|
337
386
|
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* 应用 git stash 快照但不删除快照
|
|
390
|
+
* POST /runtime/git/stash/apply
|
|
391
|
+
*/
|
|
392
|
+
gitRouter.post('/git/stash/apply', validateToken, async (req, res) => {
|
|
393
|
+
await restoreGitStash(req, res, 'apply');
|
|
394
|
+
});
|
|
395
|
+
/**
|
|
396
|
+
* 恢复 git stash 快照并删除快照
|
|
397
|
+
* POST /runtime/git/stash/pop
|
|
398
|
+
*/
|
|
399
|
+
gitRouter.post('/git/stash/pop', validateToken, async (req, res) => {
|
|
400
|
+
await restoreGitStash(req, res, 'pop');
|
|
338
401
|
});
|
|
339
402
|
/**
|
|
340
403
|
* 删除指定的 git stash
|
package/dist/routes/git.test.js
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { describe, expect, it } from 'vitest';
|
|
8
|
-
import { assertGitWorkspacePathAccessible, shouldIncludeGitDiffSummaryFile } from './git.js';
|
|
8
|
+
import { assertGitWorkspacePathAccessible, createMissingInitialCommitSnapshotError, isGitStashNoChangesOutput, isGitStashMissingInitialCommitError, parseLatestGitStash, shouldIncludeGitDiffSummaryFile, } from './git.js';
|
|
9
9
|
describe('git stash operations', () => {
|
|
10
10
|
function parseStashList(output) {
|
|
11
11
|
const stashes = [];
|
|
@@ -53,6 +53,17 @@ describe('git stash operations', () => {
|
|
|
53
53
|
it('returns empty array for empty output', () => {
|
|
54
54
|
expect(parseStashList('')).toEqual([]);
|
|
55
55
|
});
|
|
56
|
+
it('parses latest stash for no-change snapshot reuse', () => {
|
|
57
|
+
const result = parseLatestGitStash('stash@{0}: On master: 快照: 快照: t1\n');
|
|
58
|
+
expect(result).toEqual({
|
|
59
|
+
ref: 'stash@{0}',
|
|
60
|
+
message: 'On master: 快照: 快照: t1',
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
it('returns null when there is no latest stash to reuse', () => {
|
|
64
|
+
expect(parseLatestGitStash('')).toBeNull();
|
|
65
|
+
expect(parseLatestGitStash('not a stash line')).toBeNull();
|
|
66
|
+
});
|
|
56
67
|
});
|
|
57
68
|
describe('git command validation', () => {
|
|
58
69
|
it('validates workspacePath requirement', () => {
|
|
@@ -115,15 +126,28 @@ describe('git diff summary visibility', () => {
|
|
|
115
126
|
});
|
|
116
127
|
describe('error handling', () => {
|
|
117
128
|
it('detects "no local changes" error', () => {
|
|
118
|
-
|
|
119
|
-
expect(
|
|
120
|
-
expect(
|
|
129
|
+
expect(isGitStashNoChangesOutput('No local changes to save')).toBe(true);
|
|
130
|
+
expect(isGitStashNoChangesOutput('stdout: No changes to save')).toBe(true);
|
|
131
|
+
expect(isGitStashNoChangesOutput('没有本地变更需要保存')).toBe(true);
|
|
132
|
+
expect(isGitStashNoChangesOutput('other error')).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
it('detects stash failure before initial commit and returns an actionable message', () => {
|
|
135
|
+
expect(isGitStashMissingInitialCommitError('You do not have the initial commit yet\n')).toBe(true);
|
|
136
|
+
expect(isGitStashMissingInitialCommitError("fatal: bad revision 'HEAD'\n")).toBe(true);
|
|
137
|
+
expect(isGitStashMissingInitialCommitError("fatal: ambiguous argument 'HEAD': unknown revision\n")).toBe(true);
|
|
138
|
+
expect(isGitStashMissingInitialCommitError('No local changes to save')).toBe(false);
|
|
139
|
+
expect(createMissingInitialCommitSnapshotError()).toContain('请先提交一次初始提交');
|
|
121
140
|
});
|
|
122
141
|
it('detects conflict during pop', () => {
|
|
123
142
|
const hasConflict = (stdout, stderr) => stdout.includes('CONFLICT') || stderr.includes('CONFLICT');
|
|
124
143
|
expect(hasConflict('CONFLICT (content): Merge conflict', '')).toBe(true);
|
|
125
144
|
expect(hasConflict('clean merge', '')).toBe(false);
|
|
126
145
|
});
|
|
146
|
+
it('uses apply as the non-consuming restore command', () => {
|
|
147
|
+
const buildRestoreCommand = (mode, ref) => ['stash', mode, ref];
|
|
148
|
+
expect(buildRestoreCommand('apply', 'stash@{0}')).toEqual(['stash', 'apply', 'stash@{0}']);
|
|
149
|
+
expect(buildRestoreCommand('pop', 'stash@{0}')).toEqual(['stash', 'pop', 'stash@{0}']);
|
|
150
|
+
});
|
|
127
151
|
it('detects invalid stash reference', () => {
|
|
128
152
|
const isInvalidRef = (stderr) => stderr.includes('is not a valid reference');
|
|
129
153
|
expect(isInvalidRef('stash@{99} is not a valid reference')).toBe(true);
|
|
@@ -17,14 +17,14 @@ describe('properties route validation', () => {
|
|
|
17
17
|
describe('properties file operations', () => {
|
|
18
18
|
it('resolves relative propertiesPath', () => {
|
|
19
19
|
const resolvePath = (workspacePath, propertiesPath) => {
|
|
20
|
-
const defaultPath = '
|
|
20
|
+
const defaultPath = '.agentswork/my.properties';
|
|
21
21
|
const resolved = propertiesPath || defaultPath;
|
|
22
22
|
if (workspacePath && !resolved.startsWith('/'))
|
|
23
23
|
return `${workspacePath}/${resolved}`;
|
|
24
24
|
return resolved;
|
|
25
25
|
};
|
|
26
26
|
expect(resolvePath('/project', 'config/app.properties')).toBe('/project/config/app.properties');
|
|
27
|
-
expect(resolvePath('/project')).toBe('/project/
|
|
27
|
+
expect(resolvePath('/project')).toBe('/project/.agentswork/my.properties');
|
|
28
28
|
});
|
|
29
29
|
it('parses properties content', () => {
|
|
30
30
|
const parseProperties = (content) => {
|
package/dist/routes/yml.js
CHANGED
|
@@ -19,8 +19,8 @@ export const ymlRouter = Router();
|
|
|
19
19
|
ymlRouter.post('/read-yml', validateToken, async (req, res) => {
|
|
20
20
|
const { workspacePath, ymlPath } = req.body || {};
|
|
21
21
|
try {
|
|
22
|
-
const { normalizedWorkspace, resolvedTarget } = resolveWorkspaceFilePath(workspacePath, ymlPath, '
|
|
23
|
-
const providedPathStr = ymlPath ? String(ymlPath).trim() || '
|
|
22
|
+
const { normalizedWorkspace, resolvedTarget } = resolveWorkspaceFilePath(workspacePath, ymlPath, '.agentswork/my.yml');
|
|
23
|
+
const providedPathStr = ymlPath ? String(ymlPath).trim() || '.agentswork/my.yml' : '.agentswork/my.yml';
|
|
24
24
|
if (path.isAbsolute(providedPathStr)) {
|
|
25
25
|
let content = '';
|
|
26
26
|
let exists = true;
|
|
@@ -117,8 +117,8 @@ ymlRouter.post('/read-yml', validateToken, async (req, res) => {
|
|
|
117
117
|
ymlRouter.post('/load-yml', validateToken, async (req, res) => {
|
|
118
118
|
const { workspacePath, ymlPath } = req.body || {};
|
|
119
119
|
try {
|
|
120
|
-
const { normalizedWorkspace, resolvedTarget } = resolveWorkspaceFilePath(workspacePath, ymlPath, '
|
|
121
|
-
const providedPathStr = ymlPath ? String(ymlPath).trim() || '
|
|
120
|
+
const { normalizedWorkspace, resolvedTarget } = resolveWorkspaceFilePath(workspacePath, ymlPath, '.agentswork/my.yml');
|
|
121
|
+
const providedPathStr = ymlPath ? String(ymlPath).trim() || '.agentswork/my.yml' : '.agentswork/my.yml';
|
|
122
122
|
if (path.isAbsolute(providedPathStr)) {
|
|
123
123
|
let content = null;
|
|
124
124
|
let exists = true;
|
|
@@ -172,8 +172,8 @@ ymlRouter.post('/write-yml', validateToken, async (req, res) => {
|
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
174
|
try {
|
|
175
|
-
const { normalizedWorkspace, resolvedTarget: absoluteOrDefaultTarget } = resolveWorkspaceFilePath(workspacePath, ymlPath, '
|
|
176
|
-
const providedPathStr = ymlPath ? String(ymlPath).trim() || '
|
|
175
|
+
const { normalizedWorkspace, resolvedTarget: absoluteOrDefaultTarget } = resolveWorkspaceFilePath(workspacePath, ymlPath, '.agentswork/my.yml');
|
|
176
|
+
const providedPathStr = ymlPath ? String(ymlPath).trim() || '.agentswork/my.yml' : '.agentswork/my.yml';
|
|
177
177
|
if (path.isAbsolute(providedPathStr)) {
|
|
178
178
|
const resolvedTarget = absoluteOrDefaultTarget;
|
|
179
179
|
const configFileDir = path.dirname(resolvedTarget);
|
package/dist/routes/yml.test.js
CHANGED
|
@@ -27,9 +27,9 @@ describe('yml route validation', () => {
|
|
|
27
27
|
});
|
|
28
28
|
it('uses default ymlPath when not provided', () => {
|
|
29
29
|
const resolveYmlPath = (ymlPath) => {
|
|
30
|
-
return ymlPath ? String(ymlPath).trim() || '
|
|
30
|
+
return ymlPath ? String(ymlPath).trim() || '.agentswork/my.yml' : '.agentswork/my.yml';
|
|
31
31
|
};
|
|
32
|
-
expect(resolveYmlPath()).toBe('
|
|
32
|
+
expect(resolveYmlPath()).toBe('.agentswork/my.yml');
|
|
33
33
|
expect(resolveYmlPath('custom/path.yml')).toBe('custom/path.yml');
|
|
34
34
|
});
|
|
35
35
|
});
|
|
@@ -38,7 +38,7 @@ describe('response building', () => {
|
|
|
38
38
|
const response = {
|
|
39
39
|
ok: true,
|
|
40
40
|
workspacePath: '/project/workspace',
|
|
41
|
-
filePath: '/project/
|
|
41
|
+
filePath: '/project/.agentswork/my.yml',
|
|
42
42
|
exists: true,
|
|
43
43
|
content: 'agent:\n - name: test',
|
|
44
44
|
};
|
package/dist/utils/path-utils.js
CHANGED
|
@@ -40,5 +40,5 @@ export function resolveWorkspaceFilePath(workspacePath, relativeOrAbsolutePath,
|
|
|
40
40
|
* @returns 解析后的路径结果
|
|
41
41
|
*/
|
|
42
42
|
export function resolvePropertiesTargetPath(workspacePath, relativeOrAbsolutePath) {
|
|
43
|
-
return resolveWorkspaceFilePath(workspacePath, relativeOrAbsolutePath, '
|
|
43
|
+
return resolveWorkspaceFilePath(workspacePath, relativeOrAbsolutePath, '.agentswork/my.properties');
|
|
44
44
|
}
|
|
@@ -63,10 +63,10 @@ export declare function autoFillPathInYml(ymlContent: string, workspaceRelativeP
|
|
|
63
63
|
* 3. 找到匹配的配置 → 使用该配置
|
|
64
64
|
* 4. 所有层级都没有匹配的配置 → 使用最后一个找到的配置(或在工作目录创建)
|
|
65
65
|
*
|
|
66
|
-
* 例如:工作目录 D:\a\b\c\d,相对路径
|
|
67
|
-
* 第1次查找:D:\a\b\c\d\
|
|
68
|
-
* 第2次查找:D:\a\b\c\
|
|
69
|
-
* 第3次查找:D:\a\b\
|
|
66
|
+
* 例如:工作目录 D:\a\b\c\d,相对路径 .agentswork/my.yml
|
|
67
|
+
* 第1次查找:D:\a\b\c\d\.agentswork\my.yml,匹配 path="."
|
|
68
|
+
* 第2次查找:D:\a\b\c\.agentswork\my.yml,匹配 path="d"
|
|
69
|
+
* 第3次查找:D:\a\b\.agentswork\my.yml,匹配 path="c/d"
|
|
70
70
|
* ...
|
|
71
71
|
*/
|
|
72
72
|
export declare function loadYmlWithUpwardSearch(workspacePath: string, relativePath: string): Promise<YmlLoadResult>;
|
package/dist/utils/yaml-utils.js
CHANGED
|
@@ -224,10 +224,10 @@ export function autoFillPathInYml(ymlContent, workspaceRelativePath) {
|
|
|
224
224
|
* 3. 找到匹配的配置 → 使用该配置
|
|
225
225
|
* 4. 所有层级都没有匹配的配置 → 使用最后一个找到的配置(或在工作目录创建)
|
|
226
226
|
*
|
|
227
|
-
* 例如:工作目录 D:\a\b\c\d,相对路径
|
|
228
|
-
* 第1次查找:D:\a\b\c\d\
|
|
229
|
-
* 第2次查找:D:\a\b\c\
|
|
230
|
-
* 第3次查找:D:\a\b\
|
|
227
|
+
* 例如:工作目录 D:\a\b\c\d,相对路径 .agentswork/my.yml
|
|
228
|
+
* 第1次查找:D:\a\b\c\d\.agentswork\my.yml,匹配 path="."
|
|
229
|
+
* 第2次查找:D:\a\b\c\.agentswork\my.yml,匹配 path="d"
|
|
230
|
+
* 第3次查找:D:\a\b\.agentswork\my.yml,匹配 path="c/d"
|
|
231
231
|
* ...
|
|
232
232
|
*/
|
|
233
233
|
export async function loadYmlWithUpwardSearch(workspacePath, relativePath) {
|
|
@@ -272,7 +272,7 @@ agent:
|
|
|
272
272
|
describe('loadYmlWithUpwardSearch', () => {
|
|
273
273
|
const tempRoot = path.join(os.tmpdir(), `aws-yaml-test-${Date.now()}`);
|
|
274
274
|
const workspacePath = path.join(tempRoot, 'project', 'module', 'src');
|
|
275
|
-
const relativePath = '
|
|
275
|
+
const relativePath = '.agentswork/my.yml';
|
|
276
276
|
beforeEach(async () => {
|
|
277
277
|
// 创建目录结构
|
|
278
278
|
await fs.mkdir(workspacePath, { recursive: true });
|
|
@@ -32,15 +32,10 @@ export declare class McpServer {
|
|
|
32
32
|
private static readonly MEMORY_TYPES;
|
|
33
33
|
private static readonly MEMORY_TOOL_NAMES;
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* 解析记忆存储路径。
|
|
36
|
+
* 主流程:task_list/recently_memory 走 server 数据库;长期 memory 默认写入项目 .agentswork/memory/memory/<domain>.json。
|
|
37
37
|
*/
|
|
38
38
|
private static resolveMemoryStorePaths;
|
|
39
|
-
/**
|
|
40
|
-
* 生成可用于目录名的作用域片段。
|
|
41
|
-
* 主流程:裁剪输入 -> 替换路径危险字符 -> 空值落到明确 fallback。
|
|
42
|
-
*/
|
|
43
|
-
private static toSafePathSegment;
|
|
44
39
|
private getRequiredString;
|
|
45
40
|
private getOptionalString;
|
|
46
41
|
private getOptionalNumber;
|