@migor/cli 0.9.1 → 0.9.3
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/CHANGELOG.md +187 -5
- package/dist/AGENTS.md +15 -2
- package/dist/dev-child.js +84 -0
- package/dist/dev-logic.js +46 -0
- package/dist/dev-machine.js +489 -0
- package/dist/dev-runner.js +8 -1
- package/dist/dev-watch.js +223 -0
- package/dist/dev.js +377 -626
- package/dist/inspector-page.html +295 -17
- package/dist/inspector-routes.js +530 -0
- package/dist/inspector.js +44 -418
- package/dist/markdown.js +262 -0
- package/dist/native-pick.js +168 -0
- package/dist/panel-logic.js +64 -0
- package/dist/templates/package.json +2 -2
- package/dist/templates/src/app.ts +3 -0
- package/dist/templates/src/dev.config.ts +5 -0
- package/dist/templates/src/registry.ts +5 -0
- package/dist/templates/src/session-store.ts +11 -2
- package/dist/templates/src/tools/read-file/index.ts +50 -3
- package/package.json +1 -1
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { resolve, sep } from 'node:path';
|
|
3
3
|
import { Tool } from '@migor/agentia';
|
|
4
4
|
|
|
5
|
+
/** 目录清单的输出上限(条)。截断会在输出里**明示** —— 静默少给等于让模型以为目录里没有 */
|
|
6
|
+
const LIST_LIMIT = 200;
|
|
7
|
+
|
|
5
8
|
/**
|
|
6
|
-
*
|
|
9
|
+
* 工作目录的一对工具:**先看有什么(list_files)→ 再读(read_file)**。
|
|
7
10
|
*
|
|
8
11
|
* 它是脚手架自带的第二个能力,作用是把「工作目录」这条线**接通**:
|
|
9
12
|
* `agentia dev` 的面板上有「工作目录」控件,而只有当**某个能力真的消费它**时,
|
|
10
13
|
* 那个控件才有可观测的效果。做法就是这里 —— 根由 DI 注入(`WORKDIR`),
|
|
11
14
|
* 不在代码里写死。见 app.ts 的 providers。
|
|
12
15
|
*
|
|
16
|
+
* 为什么是一对而不是只有 read_file:换了目录之后,模型既不知道自己此刻在哪个目录、
|
|
17
|
+
* 也不知道里面有什么(run 的 prompt 不会变)⇒ 只能瞎猜文件名,行为看起来「没变」。
|
|
18
|
+
* `list_files` 输出的**第一行就是工作目录的绝对路径** —— 那是模型「知道自己在哪」
|
|
19
|
+
* 的通道;列出来的名字则回答「里面有什么」。
|
|
20
|
+
*
|
|
13
21
|
* 换成别的形态(把 `workdir` 换成某个 API 的 baseUrl、某个数据目录)完全一样:
|
|
14
22
|
* 值从构造器进来,方法里用 `this.root`。
|
|
15
23
|
*/
|
|
@@ -33,7 +41,41 @@ export default class ReadFileTool {
|
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
@Tool({
|
|
36
|
-
description:
|
|
44
|
+
description:
|
|
45
|
+
'列出工作目录(或其中一个相对子目录)的内容:先看这里有什么,再用 read_file 读具体文件。' +
|
|
46
|
+
'输出第一行是当前工作目录的绝对路径 —— 换过目录后用它确认自己此刻在哪个目录下工作。',
|
|
47
|
+
schema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
dir: { type: 'string', description: '相对工作目录的子目录路径(缺省 = 工作目录本身)' },
|
|
51
|
+
},
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
},
|
|
54
|
+
strict: true,
|
|
55
|
+
})
|
|
56
|
+
// 目录名带 `/` 后缀(一眼可辨),目录在前、文件在后。越界判定复用 safeResolve ——
|
|
57
|
+
// 读目录也是读,同一道闸。输出有上限,截断**明示**(不许静默少给)。
|
|
58
|
+
async list_files(input: { dir?: string }): Promise<string> {
|
|
59
|
+
const abs = safeResolve(this.root, input.dir ?? '.');
|
|
60
|
+
const entries = await readdir(abs, { withFileTypes: true });
|
|
61
|
+
const names = entries
|
|
62
|
+
.map((e) => (e.isDirectory() ? `${e.name}/` : e.name))
|
|
63
|
+
.sort((a, b) => {
|
|
64
|
+
const dirA = a.endsWith('/') ? 0 : 1;
|
|
65
|
+
const dirB = b.endsWith('/') ? 0 : 1;
|
|
66
|
+
return dirA - dirB || a.localeCompare(b);
|
|
67
|
+
});
|
|
68
|
+
const lines = [`工作目录:${this.root}`];
|
|
69
|
+
if (input.dir !== undefined && input.dir !== '.') lines.push(`所列目录:${abs}`);
|
|
70
|
+
lines.push(...names.slice(0, LIST_LIMIT));
|
|
71
|
+
if (names.length > LIST_LIMIT) {
|
|
72
|
+
lines.push(`(还有 ${names.length - LIST_LIMIT} 条未列出 —— 输出截断在 ${LIST_LIMIT} 条)`);
|
|
73
|
+
}
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Tool({
|
|
78
|
+
description: '读取工作目录下的一个文本文件(给相对路径,如 README.md);不知道文件名时先用 list_files 看',
|
|
37
79
|
schema: {
|
|
38
80
|
type: 'object',
|
|
39
81
|
properties: { rel: { type: 'string', description: '相对工作目录的文件路径' } },
|
|
@@ -45,6 +87,7 @@ export default class ReadFileTool {
|
|
|
45
87
|
// 方法名 = 模型看到的工具名 ⇒ 与其余模板一律 snake_case(doc_reviewer / note_writer …)。
|
|
46
88
|
// 顺带解掉一个坑:这里若叫 readFile,就与上面 import 进来的 readFile 同名 ——
|
|
47
89
|
// 方法体内 `this.readFile(...)` 会变成自我递归,而裸写 `readFile(...)` 读的是 import 的那个。
|
|
90
|
+
// 分工:list_files 负责「先看有什么」,这里负责「读这个具体文件」。
|
|
48
91
|
async read_file(input: { rel: string }): Promise<string> {
|
|
49
92
|
return readFile(safeResolve(this.root, input.rel), 'utf8');
|
|
50
93
|
}
|
|
@@ -59,6 +102,10 @@ export default class ReadFileTool {
|
|
|
59
102
|
* ⚠️ 两端都归一化再比:`root` 由调用方给(可能带尾斜杠),而下面判越界用的是
|
|
60
103
|
* **字符串前缀** —— `resolve(root) + sep` 不归一化时会是 `/a/b//`,于是每一次调用
|
|
61
104
|
* 都误报越界。归一化放在函数内部(不只依赖构造器):本函数是导出的,调用方可能不止一个。
|
|
105
|
+
*
|
|
106
|
+
* ⚠️ 已知边界:**不解 realpath** —— 工作目录**内**的符号链接可以指到目录外。
|
|
107
|
+
* 它防的是「模型乱给相对路径跑出工作目录」,不是一道安全边界(read-only,危害有限);
|
|
108
|
+
* 真要拿它当边界用(比如可写工具),先在这里补 realpath 比较。
|
|
62
109
|
*/
|
|
63
110
|
export function safeResolve(root: string, rel: string): string {
|
|
64
111
|
const base = resolve(root);
|