@migor/cli 0.7.2 → 0.8.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.
- package/CHANGELOG.md +53 -1
- package/README.md +18 -12
- package/dist/AGENTS.md +26 -6
- package/dist/cli.js +39 -11
- package/dist/create.js +6 -1
- package/dist/dev.js +8 -3
- package/dist/diff.js +23 -1
- package/dist/doctor.js +33 -2
- package/dist/inspector/fromTrace.js +7 -7
- package/dist/inspector/summary.js +1 -1
- package/dist/inspector/trace-view.css +2 -0
- package/dist/inspector/view.js +3 -3
- package/dist/inspector-page.html +1 -0
- package/dist/inspector.js +1 -1
- package/dist/report.js +36 -15
- package/dist/templates/README.md +62 -0
- package/dist/templates/capabilities/prompt/asset.md +3 -0
- package/dist/templates/capabilities/prompt/index.ts +9 -0
- package/dist/templates/capabilities/skill/index.ts +19 -0
- package/dist/templates/capabilities/subagent/index.ts +17 -0
- package/dist/templates/capabilities/subagent/system.md +3 -0
- package/dist/templates/capabilities/tool/index.ts +18 -0
- package/dist/templates/env +3 -0
- package/dist/templates/env.example +6 -0
- package/dist/templates/gitignore +6 -0
- package/dist/templates/package.json +20 -0
- package/dist/templates/scripts/copy-assets.mjs +26 -0
- package/dist/templates/src/main.ts +37 -0
- package/dist/templates/src/registry.ts +10 -0
- package/dist/templates/tsconfig.json +21 -0
- package/dist/templates.js +92 -270
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,57 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.8.0] - 2026-09-21
|
|
9
|
+
|
|
10
|
+
### 变更
|
|
11
|
+
|
|
12
|
+
> 本版主题(窗口 `0.7.2 → 0.8.0`,含 #83–#105):**CLI 机器可读面 + 脚手架生产路径修复**。
|
|
13
|
+
> 框架运行时 API 零变化;窗口内 23 个提交里 13 个是纯结构拆分(AsyncRunner / turn / loop /
|
|
14
|
+
> http 外移判定面,零行为变化),其余大多是仓库自身的门禁与测试加固。
|
|
15
|
+
> **一条必须看的修复**:用 ≤0.7.2 的 `agentia create` 生成过工程的,
|
|
16
|
+
> `npm run build && npm start` 一跑就崩(discover 目录是 cwd 相对写法,生产形态下会去加载
|
|
17
|
+
> `src/` 的 `.ts` 源码)。缺陷在**生成出来的工程里**,升级 CLI 不会自动修好已有工程 ——
|
|
18
|
+
> 迁移办法见下方「迁移」。
|
|
19
|
+
|
|
20
|
+
### 新增(CLI)
|
|
21
|
+
|
|
22
|
+
- **`agentia --version` / `-v`**:打印 CLI 版本(读包自身 `package.json`)。
|
|
23
|
+
- **`report` / `diff` / `doctor` 支持 `--json`**:stdout 只输出一个 JSON 文档,可直接进
|
|
24
|
+
管道与 CI;出错仍走 stderr + 退出码 1,且 stdout 保持空。`harvest` 刻意不加
|
|
25
|
+
(它的 stdout 本身就是产物);`dev` 额外参数原样透传给用户脚本。
|
|
26
|
+
- **脚手架把 `@migor/cli` 写进生成工程的 devDependencies**(与框架同 `^` 版本),`dev`
|
|
27
|
+
script 改为 `agentia dev`:`npx agentia …` 走本地 bin —— 离线可用,且版本被 pin 住与
|
|
28
|
+
框架同批(不 pin 的话老工程会被 npx 拉到最新 CLI)。
|
|
29
|
+
|
|
30
|
+
### 修复
|
|
31
|
+
|
|
32
|
+
- **脚手架生成工程的 discover 目录按本文件位置解析,不再是 cwd 相对字符串**(本版最高
|
|
33
|
+
优先级):旧模板生成的工程 `npm run build && npm start` 必崩
|
|
34
|
+
"Invalid or unexpected token"(生产形态下去加载 `src/` 的 `.ts` 源码,而装饰器不是
|
|
35
|
+
可擦除语法),换个 cwd 启动连目录都找不到。**0.7.2 及之前所有版本生成的工程都带此
|
|
36
|
+
缺陷。** 新写法按 `import.meta.url` 相对本文件解析(开发态 `src/`、构建后 `dist/` 都
|
|
37
|
+
成立),并过滤不存在的分类目录(空分类 tsc 不产出 `dist/<分类>/`,「这类暂时没有能力」
|
|
38
|
+
不该让启动失败)。
|
|
39
|
+
|
|
40
|
+
### 迁移
|
|
41
|
+
|
|
42
|
+
- **用 ≤0.7.2 的 `agentia create` 生成过工程的**:把工程 `src/main.ts` 的 discover 段
|
|
43
|
+
换成新模板的写法(重新 `agentia create` 一个同名工程对照抄过来即可 —— 核心是目录按
|
|
44
|
+
`new URL(d + '/', import.meta.url)` 解析 + `existsSync` 过滤这两处)。
|
|
45
|
+
框架 API 本身无破坏性变更,`@migor/agentia` 与 `@migor/cli` 升到 `^0.8.0` 即可。
|
|
46
|
+
|
|
47
|
+
### 内部(不影响使用者)
|
|
48
|
+
|
|
49
|
+
- 纯结构拆分 13 件:AsyncRunner 五步(SlotPool / approval-policy / DrainGate /
|
|
50
|
+
resume-policy / TaskWaiters)、`turn.ts` 四步、`loop.ts` 三步、`http.ts` 两步 ——
|
|
51
|
+
判定面有名字、编排留在原处,零行为变化(#85–#100)。
|
|
52
|
+
- Biome 零告警闸门:93 warnings + 12 infos → 0,verify-all 第 1 步与 CI lint job 翻
|
|
53
|
+
`--error-on-warnings`(#84)。
|
|
54
|
+
- 计时断言不再卡预算边界、改用单调时钟量(#102 / #103)。
|
|
55
|
+
- 覆盖率棘轮门禁(c8:行 95 / 分支 85 / 函数 92)+ e2e-cli 折入「npm pack → 离线安装 →
|
|
56
|
+
装出来的包真跑最小 run」(#104)。
|
|
57
|
+
- CLI 模板从字符串升级为真文件(`packages/cli/templates/`,生成产物逐字节不变)(#105)。
|
|
58
|
+
|
|
8
59
|
## [0.7.2] - 2026-09-20
|
|
9
60
|
|
|
10
61
|
### 变更
|
|
@@ -676,7 +727,8 @@
|
|
|
676
727
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
677
728
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
678
729
|
|
|
679
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.
|
|
730
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.8.0...HEAD
|
|
731
|
+
[0.8.0]: https://github.com/retrychx/agentia/releases/tag/v0.8.0
|
|
680
732
|
[0.7.2]: https://github.com/retrychx/agentia/releases/tag/v0.7.2
|
|
681
733
|
[0.7.1]: https://github.com/retrychx/agentia/releases/tag/v0.7.1
|
|
682
734
|
[0.7.0]: https://github.com/retrychx/agentia/releases/tag/v0.7.0
|
package/README.md
CHANGED
|
@@ -3,26 +3,32 @@
|
|
|
3
3
|
Agentia 框架的命令行工具:脚手架、能力生成、本地调试与 **trace 观测**。
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
agentia
|
|
9
|
-
|
|
10
|
-
agentia
|
|
11
|
-
agentia
|
|
12
|
-
agentia
|
|
13
|
-
agentia add <pkg> # 安装第三方能力包并登记
|
|
6
|
+
npx @migor/cli create my-app # 脚手架:四分类目录 + src/registry.ts + src/main.ts
|
|
7
|
+
cd my-app && npm install # 框架与 CLI 都进工程(CLI 在 devDependencies)
|
|
8
|
+
npx agentia g subagent doc-reviewer # 生成能力并自动登记到注册表
|
|
9
|
+
npm run dev # = agentia dev:tsx watch + 本地 inspector 面板
|
|
10
|
+
npx agentia doctor # 装配体检:未登记 / 悬空能力、命名规范、重复条目
|
|
11
|
+
npx agentia add <pkg> # 安装第三方能力包并登记
|
|
12
|
+
npx agentia --version # 版本(= -v)
|
|
14
13
|
```
|
|
15
14
|
|
|
15
|
+
> 首次创建必须带 scope(`npx @migor/cli`)—— npm 上另有一个别人的 `agentia` 包。
|
|
16
|
+
> 工程内用短名 `npx agentia` 走的是本地 bin(CLI 已进 `devDependencies`:离线可用、版本与框架同批 pin)。
|
|
17
|
+
> 想全局装:`npm i -g @migor/cli`。
|
|
18
|
+
|
|
16
19
|
## 命令
|
|
17
20
|
|
|
18
21
|
| 命令 | 作用 |
|
|
19
22
|
|---|---|
|
|
20
|
-
| `agentia create <name>` | 脚手架新项目:`src/tools` · `src/skills` · `src/prompts` · `src/subagents` 四分类目录、`src/registry.ts` 注册表、`src/main.ts` 入口、tsconfig |
|
|
23
|
+
| `agentia create <name>` | 脚手架新项目:`src/tools` · `src/skills` · `src/prompts` · `src/subagents` 四分类目录、`src/registry.ts` 注册表、`src/main.ts` 入口、tsconfig。CLI 本身也装进工程的 `devDependencies` |
|
|
21
24
|
| `agentia g <type> <name>` | 生成能力(`tool` / `skill` / `subagent` / `prompt`)到对应分类目录 `src/<分类>/<name>/` 并登记注册表;长文本资产(`system.md` / `asset.md`)一并生成 |
|
|
22
|
-
| `agentia dev` | `tsx watch` 启动 `src/main.ts`,改能力文件自动重启;内建 inspector 面板查看 trace |
|
|
23
|
-
| `agentia doctor` |
|
|
24
|
-
| `agentia report <trace.jsonl
|
|
25
|
+
| `agentia dev [-- <脚本参数…>]` | `tsx watch` 启动 `src/main.ts`,改能力文件自动重启;内建 inspector 面板查看 trace。额外参数原样透传给脚本(`npm run dev -- "问题"` 就是这么走的) |
|
|
26
|
+
| `agentia doctor [--json]` | 纯静态体检,不加载用户代码。`--json` 出结构化结果(有错误仍退出 1) |
|
|
27
|
+
| `agentia report <trace.jsonl> [--json]` | 从 trace 落盘文件生成调优报告(能力耗时 / 成本 / 错误率排行)。`--json` 出单个 JSON 文档,便于 CI 断言 |
|
|
28
|
+
| `agentia harvest <trace.jsonl>` | trace → eval 用例骨架(`--out` / `--force` / `--failed` / `--limit`)。**无 `--json`**:它的 stdout 本身就是产物 |
|
|
29
|
+
| `agentia diff <a.jsonl> <b.jsonl> [--json]` | 两条 trace 的调用树 A/B 比对(有差异时退出码 1)。`--json` 出结构化差异,退出码语义不变 |
|
|
25
30
|
| `agentia add <pkg>` | 安装第三方能力包(`defineModule` 能力包)并登记到注册表 |
|
|
31
|
+
| `agentia --version` / `-v` | 报出 CLI 版本(读包自身 `package.json`,不另存常量) |
|
|
26
32
|
|
|
27
33
|
## 可观测(trace)
|
|
28
34
|
|
package/dist/AGENTS.md
CHANGED
|
@@ -80,16 +80,28 @@ console.log(result.finalText, result.stopReason, result.trace.totalUsage);
|
|
|
80
80
|
## 2. 项目结构(CLI 约定)
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
npx @migor/cli create my-app #
|
|
84
|
-
cd my-app && npm install
|
|
83
|
+
npx @migor/cli create my-app # 首次创建:必须带 scope(短名 agentia 在 npm 上是别人的包)
|
|
84
|
+
cd my-app && npm install # 框架与 CLI 都装进工程
|
|
85
85
|
$EDITOR .env # 填 ANTHROPIC_API_KEY(脚手架已生成,且已被 .gitignore 挡住)
|
|
86
|
-
|
|
87
|
-
npx
|
|
88
|
-
npx
|
|
86
|
+
npm run dev # = agentia dev:tsx watch + 本地 inspector 面板
|
|
87
|
+
npx agentia g tool fetch-weather # 生成到 src/tools/fetch-weather/(skill/prompt/subagent 同理)
|
|
88
|
+
npx agentia doctor # 静态体检(未登记/悬空/命名/重复)
|
|
89
|
+
npx agentia --version # CLI 版本(= -v)
|
|
89
90
|
```
|
|
90
91
|
|
|
92
|
+
> **命令从哪来**:脚手架把 `@migor/cli` 装进工程的 `devDependencies`,所以**工程内**用短名
|
|
93
|
+
> `npx agentia …` 即可(走本地 bin:离线可用、版本与工程一同 pin)。**首次创建**必须用带 scope 的
|
|
94
|
+
> `npx @migor/cli create` —— npm 上另有一个别人的 `agentia` 包,短名会装错东西。
|
|
95
|
+
> 想全局装上(到处都能敲 `agentia`):`npm i -g @migor/cli`。
|
|
96
|
+
|
|
91
97
|
四分类目录,一能力一文件夹:`src/tools/` · `src/skills/` · `src/prompts/` · `src/subagents/` —— **目录名就是类型**,不用记别名。每个文件夹的 `index.ts` 是入口,`default export` 支持三种形态:**类**(token = 文件夹名)、**Provider 对象**、**Provider 数组**。显式注册表在 `src/registry.ts`(`agentia g` 自动维护,也可手改)。
|
|
92
98
|
|
|
99
|
+
> **脚手架 `src/main.ts` 怎么找这些目录**:按**本文件位置**解析(`fileURLToPath(new URL('tools/', import.meta.url))`),
|
|
100
|
+
> 所以 dev 解析到 `src/`、`npm run build` 之后解析到 `dist/` —— 从任何目录启动都成立,也不受 cwd 影响。
|
|
101
|
+
> 别改成 cwd 相对写法(形如 `src/tools` 的字符串):那样 `node dist/main.js` 会去加载 `src/` 下的 `.ts`
|
|
102
|
+
> 源码,而装饰器不是可擦除的类型语法,Node 直接跑不了。空分类目录(还没有该类型的能力 ⇒ 构建后没有
|
|
103
|
+
> 对应 `dist/<分类>/`)要过滤掉,否则 `discover` 会因「显式给出的路径不存在」而报错。
|
|
104
|
+
|
|
93
105
|
> **陷阱**:装饰器注册表是模块级 `WeakMap`。框架必须是**单一模块实例** —— 混用 `src` 与 `dist`、或在一个仓库里装两份 agentia,会让能力收集为空。让 CLI 生成的 `package.json` 里只依赖一份框架即可。
|
|
94
106
|
|
|
95
107
|
---
|
|
@@ -772,6 +784,9 @@ console.log(renderRunReport(report));
|
|
|
772
784
|
|
|
773
785
|
CLI 侧有薄壳:`agentia report <trace.jsonl>` —— 每行一个 JSON(裸 Trace,或含 `result.trace` /
|
|
774
786
|
`trace` 的 TaskRecord,如 `FileTaskStore` 的导出),跨行按能力合并后打印排行。
|
|
787
|
+
加 `--json` 得到机器可读输出:stdout 只有一个 JSON 文档(`{ file, runs, failed, skippedLines,
|
|
788
|
+
failures, capabilities, totals }`)、没有人类装饰,适合脚本与 CI 断言;出错仍走 stderr
|
|
789
|
+
(`错误:…`)并把退出码置 1、stdout 保持为空 —— 脚本据此区分「有结果」与「没跑成」。
|
|
775
790
|
|
|
776
791
|
> ⚠️ **单条 run 内样本常 < 5,分位没有意义** —— 所以报告以 `total` / `max` 为主;
|
|
777
792
|
> 要看分位请用 `mergeRunReports` 汇总多条,或用 `metricsSink` 的直方图。
|
|
@@ -976,7 +991,12 @@ const c = await app.run(messages); // 起一条新 run,沿着分叉点前的
|
|
|
976
991
|
|
|
977
992
|
不落代码也可以直比两份 trace 导出:`agentia diff a.jsonl b.jsonl`(输入形态同 `agentia report`),
|
|
978
993
|
打印 run 级 summary + 逐 span 差异,**有差异时退出码 1**(diff(1) 语义)——可直接进 CI 挡
|
|
979
|
-
「换 prompt /
|
|
994
|
+
「换 prompt / 模型后轨迹漂移」。`--json` 给出结构化差异(`{ a, b, equal, summary, spans }`,
|
|
995
|
+
每个 span 带 `path` / `missing` / `fields`),**退出码语义不变**(有差异仍为 1)—— 门禁照旧可用,
|
|
996
|
+
只是「差在哪」也能被读。`agentia doctor --json` 同理(体检结果结构化,有错误仍退出 1)。
|
|
997
|
+
|
|
998
|
+
> `report` / `diff` / `doctor` 支持 `--json`;`harvest` **没有**:它的 stdout 本身就是产物
|
|
999
|
+
> (生成的 eval 文件源码),加 `--json` 会自相矛盾。
|
|
980
1000
|
|
|
981
1001
|
- **配对语义**(结构性配对,字段差异不影响配对):llm.turn 按回合序配对、**忽略 span name** ——
|
|
982
1002
|
name 是模型 id,而「换模型重跑」正是 A/B 主用例,按 name 配对会把两侧所有 turn 报成缺失;
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** agentia CLI 入口:手写参数解析 + 命令分发(零依赖) */
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { createProject } from './create.js';
|
|
4
6
|
import { generateCapability } from './generate.js';
|
|
5
7
|
import { isCapabilityType, isValidName, CAPABILITY_TYPES } from './templates.js';
|
|
@@ -17,14 +19,18 @@ const USAGE = `agentia —— Agentia 框架命令行工具
|
|
|
17
19
|
agentia g <type> <name> 在当前目录生成能力(别名:generate)
|
|
18
20
|
type: ${CAPABILITY_TYPES.join(' | ')}
|
|
19
21
|
agentia dev 启动开发模式(tsx watch 热重载 + 本地 inspector 面板)
|
|
20
|
-
agentia doctor
|
|
21
|
-
agentia report <trace.jsonl>
|
|
22
|
+
agentia doctor [--json] 装配体检(未登记/悬空能力/命名规范/重复条目)
|
|
23
|
+
agentia report <trace.jsonl> [--json] 从 trace 落盘文件生成调优报告(能力耗时/成本/错误率排行)
|
|
22
24
|
agentia harvest <trace.jsonl> 把线上 trace 翻成 eval 用例骨架
|
|
23
|
-
([--out <file.ts>] [--failed] [--limit N])
|
|
24
|
-
agentia diff <a.jsonl> <b.jsonl>
|
|
25
|
+
([--out <file.ts>] [--force] [--failed] [--limit N])
|
|
26
|
+
agentia diff <a.jsonl> <b.jsonl> [--json] 两条 trace 的调用树 A/B 比对(有差异时退出码 1)
|
|
25
27
|
agentia add <pkg> 安装第三方能力包并登记到 src/registry.ts
|
|
26
28
|
agentia --help 显示本帮助
|
|
29
|
+
agentia --version 显示版本(等价 -v)
|
|
27
30
|
|
|
31
|
+
--json:report / diff / doctor 的机器可读输出 —— stdout 只有一个 JSON 文档、无人类装饰,
|
|
32
|
+
便于脚本与 CI 串接;出错仍走 stderr + 退出码 1。(harvest 没有 --json:它的 stdout
|
|
33
|
+
本身就是产物,即生成的 eval 文件源码。)
|
|
28
34
|
name 规则:小写字母开头的小写 kebab-case(如 hello、doc-reviewer)
|
|
29
35
|
`;
|
|
30
36
|
// 各子命令自己的用法串 —— 同时供 fail() 与「子命令级 --help」使用(单源,不写两遍)。
|
|
@@ -38,7 +44,7 @@ const SUB_USAGE = {
|
|
|
38
44
|
g: G_USAGE,
|
|
39
45
|
generate: G_USAGE,
|
|
40
46
|
dev: '用法:agentia dev',
|
|
41
|
-
doctor: '用法:agentia doctor',
|
|
47
|
+
doctor: '用法:agentia doctor [--json]',
|
|
42
48
|
report: REPORT_USAGE,
|
|
43
49
|
harvest: HARVEST_USAGE,
|
|
44
50
|
diff: DIFF_USAGE,
|
|
@@ -49,6 +55,20 @@ function fail(message) {
|
|
|
49
55
|
process.exitCode = 1;
|
|
50
56
|
return 1;
|
|
51
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* CLI 版本 = 本包 package.json 的 `version`(**单源**:`scripts/check-release.mjs` 已保证它
|
|
60
|
+
* 与根包一致,所以这里不另存一份常量 —— 常量会漂,读到的文件不会)。
|
|
61
|
+
* 源码直跑(`src/cli.ts`)与发布物(`dist/cli.js`)的 `../package.json` 都是包根那一份。
|
|
62
|
+
*/
|
|
63
|
+
function readCliVersion() {
|
|
64
|
+
try {
|
|
65
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url)), 'utf8'));
|
|
66
|
+
return pkg.version ?? 'unknown';
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return 'unknown';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
52
72
|
function checkName(name) {
|
|
53
73
|
if (name === undefined)
|
|
54
74
|
return false;
|
|
@@ -60,6 +80,13 @@ function main(argv) {
|
|
|
60
80
|
console.log(USAGE);
|
|
61
81
|
return 0;
|
|
62
82
|
}
|
|
83
|
+
// 版本:`--version` / `-v`。此前只有 `--help`,用户问不出自己装的是哪一版 —— 而本仓库
|
|
84
|
+
// 把版本纪律做到了发布面+闸门,CLI 却报不出自己的版本,是个说不通的缺口(bug 报告第一句
|
|
85
|
+
// 就是「你装的哪一版」)。
|
|
86
|
+
if (command === '--version' || command === '-v') {
|
|
87
|
+
console.log(readCliVersion());
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
63
90
|
// 子命令级 --help:此前 `agentia report --help` 会把 --help 当成「要读的文件名」,
|
|
64
91
|
// 用户拿到的是 ENOENT 而不是该命令的用法(report / harvest / diff 尤其需要)。
|
|
65
92
|
const subUsage = SUB_USAGE[command];
|
|
@@ -109,14 +136,15 @@ function main(argv) {
|
|
|
109
136
|
}
|
|
110
137
|
}
|
|
111
138
|
if (command === 'dev') {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return devServer();
|
|
139
|
+
// 额外参数透传给用户脚本:脚手架把 `npm run dev` 指向本命令,而工程 README 文档化的
|
|
140
|
+
// 用法是 `npm run dev -- "你的问题"` —— 参数必须原样传下去,不能当未知参数拒掉。
|
|
141
|
+
return devServer(rest);
|
|
115
142
|
}
|
|
116
143
|
if (command === 'doctor') {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
144
|
+
const extra = rest.filter((a) => a !== '--json');
|
|
145
|
+
if (extra.length > 0)
|
|
146
|
+
return fail(`未知参数:${extra[0]}`);
|
|
147
|
+
return doctor({ json: rest.includes('--json') });
|
|
120
148
|
}
|
|
121
149
|
if (command === 'report') {
|
|
122
150
|
// 异步命令(要 dynamic import 构建期拷进来的聚合实现):自己设 exitCode,
|
package/dist/create.js
CHANGED
|
@@ -79,12 +79,17 @@ export function createProject(name, parent) {
|
|
|
79
79
|
cd ${dir}
|
|
80
80
|
npm install
|
|
81
81
|
把 API key 填进 .env(已生成,且已被 .gitignore 忽略)
|
|
82
|
-
npm run dev
|
|
82
|
+
npm run dev # = agentia dev:tsx watch + 本地 inspector 面板
|
|
83
83
|
|
|
84
84
|
生产构建:npm run build && npm start(tsc → dist/,.md 资产由 scripts/copy-assets.mjs 跟随拷贝)
|
|
85
|
+
dev 跑 src/、start 跑 dist/ —— 能力目录按文件位置解析,两边都成立。
|
|
85
86
|
|
|
86
87
|
目录约定:src/tools/ · src/skills/ · src/prompts/ · src/subagents/(一能力一文件夹)
|
|
87
88
|
|
|
89
|
+
CLI 已装进本工程(devDependencies),所以在工程内直接 npx agentia g / doctor / dev 即可
|
|
90
|
+
(走本地 bin,离线可用、版本与工程一同 pin)。注意创建时要用带 scope 的 npx @migor/cli create
|
|
91
|
+
—— 短名 agentia 在 npm 上是别人的包。
|
|
92
|
+
|
|
88
93
|
提示:项目内 AGENTS.md 是本框架的使用说明(API 速查 + 已知边界),
|
|
89
94
|
交给 AI 辅助编码时会自动被读,能显著减少猜 API 的错。`);
|
|
90
95
|
return 0;
|
package/dist/dev.js
CHANGED
|
@@ -26,7 +26,11 @@ function supportsImport() {
|
|
|
26
26
|
return minor >= 19;
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @param argv 透传给用户脚本的参数(`agentia dev -- "问题"`)。脚手架把 `npm run dev` 指到本
|
|
31
|
+
* 命令,而工程 README 文档化的用法正是 `npm run dev -- "你的问题"` —— 不能把参数吃掉。
|
|
32
|
+
*/
|
|
33
|
+
export function devServer(argv = []) {
|
|
30
34
|
const cwd = process.cwd();
|
|
31
35
|
const entry = join(cwd, 'src', 'main.ts');
|
|
32
36
|
if (!existsSync(entry)) {
|
|
@@ -44,8 +48,9 @@ export function devServer() {
|
|
|
44
48
|
process.on('SIGINT', forward('SIGINT'));
|
|
45
49
|
process.on('SIGTERM', forward('SIGTERM'));
|
|
46
50
|
const startChild = (env) => {
|
|
47
|
-
//
|
|
48
|
-
|
|
51
|
+
// argv 是用户输入(`npm run dev -- "问题"`)⇒ 正是 npmSpawn 逐参数脱敏的存在理由:
|
|
52
|
+
// win32 下走 cmd.exe 包装并转义(shell:true 不转义参数,见 npm-bin.ts)
|
|
53
|
+
const spec = npmSpawn('npx', ['tsx', 'watch', 'src/main.ts', ...argv]);
|
|
49
54
|
child = spawn(spec.command, spec.args, { cwd, stdio: 'inherit', env, ...spec.options });
|
|
50
55
|
child.on('error', (err) => {
|
|
51
56
|
console.error(`错误:启动 dev 失败:${err.message}`);
|
package/dist/diff.js
CHANGED
|
@@ -217,7 +217,7 @@ function pushEventDiffs(out, x, y) {
|
|
|
217
217
|
}
|
|
218
218
|
/* ── 命令本体:两个 JSONL 各取第一条 trace → diff → 人读输出 ──────────────────── */
|
|
219
219
|
/** 用法串(cli.ts 的子命令 `--help` 也从这里取,避免两处各写一份) */
|
|
220
|
-
export const USAGE = '用法:agentia diff <a.jsonl> <b.jsonl>';
|
|
220
|
+
export const USAGE = '用法:agentia diff <a.jsonl> <b.jsonl> [--json]';
|
|
221
221
|
/** 逐行 JSON.parse + extractTrace(同 harvest),取第一条可提取的 trace;坏行跳过计数 */
|
|
222
222
|
async function readFirstTrace(file) {
|
|
223
223
|
let raw;
|
|
@@ -254,8 +254,13 @@ function fmtEntry(e) {
|
|
|
254
254
|
}
|
|
255
255
|
/** 失败一律**抛错**(同 report/harvest 的受理模式);「有差异」不是错误,就地设 exitCode = 1 */
|
|
256
256
|
export async function diffCommand(args) {
|
|
257
|
+
// `--json`:机器可读输出(stdout 只有一个 JSON 文档,无任何人类装饰);退出码语义不变 ——
|
|
258
|
+
// 有差异仍退出 1(脚本照旧可拿退出码当门禁,`--json` 只是让「差在哪」也能被读)。
|
|
259
|
+
const json = args.includes('--json');
|
|
257
260
|
const files = [];
|
|
258
261
|
for (const a of args) {
|
|
262
|
+
if (a === '--json')
|
|
263
|
+
continue;
|
|
259
264
|
if (a.startsWith('--'))
|
|
260
265
|
throw new Error(`未知参数:${a}\n${USAGE}`);
|
|
261
266
|
files.push(a);
|
|
@@ -266,6 +271,23 @@ export async function diffCommand(args) {
|
|
|
266
271
|
const ra = await readFirstTrace(fileA);
|
|
267
272
|
const rb = await readFirstTrace(fileB);
|
|
268
273
|
const d = diffTraces(ra.trace, rb.trace);
|
|
274
|
+
if (json) {
|
|
275
|
+
const payload = {
|
|
276
|
+
a: { file: fileA, traceId: ra.trace.traceId ?? null, skippedLines: ra.badLines },
|
|
277
|
+
b: { file: fileB, traceId: rb.trace.traceId ?? null, skippedLines: rb.badLines },
|
|
278
|
+
equal: d.equal,
|
|
279
|
+
summary: d.summary,
|
|
280
|
+
spans: d.spans.map((s) => ({
|
|
281
|
+
path: s.path,
|
|
282
|
+
missing: s.a === undefined ? 'a' : s.b === undefined ? 'b' : null,
|
|
283
|
+
fields: s.fields,
|
|
284
|
+
})),
|
|
285
|
+
};
|
|
286
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
287
|
+
if (!d.equal)
|
|
288
|
+
process.exitCode = 1;
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
269
291
|
const badNote = (n) => (n > 0 ? `(跳过无法解析 ${n} 行)` : '');
|
|
270
292
|
const lines = [
|
|
271
293
|
`trace a ${fileA}(${ra.trace.traceId ?? '(无 traceId)'})${badNote(ra.badLines)}`,
|
package/dist/doctor.js
CHANGED
|
@@ -56,13 +56,30 @@ function parseRegistry(content) {
|
|
|
56
56
|
}
|
|
57
57
|
/** 分类目录相对注册表的 import 前缀:./tools 等(悬空条目判定用) */
|
|
58
58
|
const LOCAL_PREFIXES = CAPABILITY_DIR_LIST.map((p) => './' + p.replace(/^src\//, '') + '/');
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* @param opts.json `--json`:机器可读输出(stdout 只有一个 JSON 文档,无人类横幅)。
|
|
61
|
+
* 退出码语义不变 —— 有错误仍退出 1。
|
|
62
|
+
*/
|
|
63
|
+
export function doctor(opts = {}) {
|
|
64
|
+
const json = opts.json === true;
|
|
60
65
|
const cwd = process.cwd();
|
|
61
66
|
// 老布局:先给迁移提示再收工 —— 去扫一个还不存在的新布局只会刷一屏无关警告
|
|
62
67
|
const legacy = legacyLayout(cwd);
|
|
63
68
|
if (legacy) {
|
|
69
|
+
const hint = legacyMigrationHint(legacy);
|
|
70
|
+
if (json) {
|
|
71
|
+
const payload = {
|
|
72
|
+
ok: [],
|
|
73
|
+
warnings: [hint],
|
|
74
|
+
errors: [],
|
|
75
|
+
legacyLayoutHint: hint,
|
|
76
|
+
summary: { errors: 0, warnings: 1 },
|
|
77
|
+
};
|
|
78
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
64
81
|
console.log('agentia doctor —— 装配体检\n');
|
|
65
|
-
console.log(` 警告:${
|
|
82
|
+
console.log(` 警告:${hint}`);
|
|
66
83
|
console.log('\n体检结果:0 错误,1 警告');
|
|
67
84
|
return 0;
|
|
68
85
|
}
|
|
@@ -136,6 +153,20 @@ export function doctor() {
|
|
|
136
153
|
warnings.push(`${REGISTRY_PATH} 条目 { provide: '${e.token}' } 指向 ${source},但该目录已不存在(悬空条目,请删除条目或恢复目录)`);
|
|
137
154
|
}
|
|
138
155
|
}
|
|
156
|
+
if (json) {
|
|
157
|
+
const payload = {
|
|
158
|
+
ok: oks,
|
|
159
|
+
warnings,
|
|
160
|
+
errors,
|
|
161
|
+
summary: { errors: errors.length, warnings: warnings.length },
|
|
162
|
+
};
|
|
163
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
164
|
+
if (errors.length > 0) {
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
139
170
|
console.log('agentia doctor —— 装配体检\n');
|
|
140
171
|
for (const msg of oks)
|
|
141
172
|
console.log(` ✓ ${msg}`);
|
|
@@ -59,9 +59,9 @@ function eventToolName(evName, bodyTool, typeMap) {
|
|
|
59
59
|
/** 事件摘要:input 走 fmtArg(与 span 入参同一形态),output 走文本内容 */
|
|
60
60
|
function eventText(ev) {
|
|
61
61
|
const body = ev.body;
|
|
62
|
-
if (ev.name === 'tool.input') return fmtArg(body
|
|
62
|
+
if (ev.name === 'tool.input') return fmtArg(body?.input);
|
|
63
63
|
if (ev.name === 'tool.output') {
|
|
64
|
-
const c = body
|
|
64
|
+
const c = body?.content;
|
|
65
65
|
return typeof c === 'string' ? c : c == null ? '' : JSON.stringify(c);
|
|
66
66
|
}
|
|
67
67
|
return body == null ? '' : typeof body === 'string' ? body : JSON.stringify(body);
|
|
@@ -76,7 +76,7 @@ function eventText(ev) {
|
|
|
76
76
|
* 原文口径统一在 view.js 的 `rawArg`:官网 playground 的模拟回放也要用它,两处一处定义。
|
|
77
77
|
*/
|
|
78
78
|
function eventFull(ev) {
|
|
79
|
-
if (ev.name === 'tool.input') return rawArg(ev.body
|
|
79
|
+
if (ev.name === 'tool.input') return rawArg(ev.body?.input);
|
|
80
80
|
return eventText(ev);
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -85,7 +85,7 @@ function eventFull(ev) {
|
|
|
85
85
|
* 返回 false 表示 trace 空、未做任何渲染。
|
|
86
86
|
*/
|
|
87
87
|
export function playTrace(view, trace) {
|
|
88
|
-
const spans = Array.isArray(trace
|
|
88
|
+
const spans = Array.isArray(trace?.spans) ? trace.spans : [];
|
|
89
89
|
if (!spans.length) return false;
|
|
90
90
|
|
|
91
91
|
const byId = new Map(spans.map((s) => [s.spanId, s]));
|
|
@@ -102,7 +102,7 @@ export function playTrace(view, trace) {
|
|
|
102
102
|
let d = 0;
|
|
103
103
|
let cur = s;
|
|
104
104
|
const seen = new Set();
|
|
105
|
-
while (cur
|
|
105
|
+
while (cur?.parentSpanId && byId.has(cur.parentSpanId) && !seen.has(cur.spanId)) {
|
|
106
106
|
seen.add(cur.spanId);
|
|
107
107
|
d += 1;
|
|
108
108
|
cur = byId.get(cur.parentSpanId);
|
|
@@ -141,9 +141,9 @@ export function playTrace(view, trace) {
|
|
|
141
141
|
view.event(
|
|
142
142
|
it.s.spanId,
|
|
143
143
|
it.ev.name,
|
|
144
|
-
eventToolName(it.ev.name, it.ev.body
|
|
144
|
+
eventToolName(it.ev.name, it.ev.body?.tool, typeMap),
|
|
145
145
|
eventText(it.ev),
|
|
146
|
-
|
|
146
|
+
it.ev.body?.ok !== false,
|
|
147
147
|
eventFull(it.ev),
|
|
148
148
|
);
|
|
149
149
|
} else {
|
|
@@ -40,7 +40,7 @@ export function summarizeTrace(trace) {
|
|
|
40
40
|
if (v > a.maxMs) a.maxMs = v;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const spans =
|
|
43
|
+
const spans = trace?.spans || [];
|
|
44
44
|
for (const span of spans) {
|
|
45
45
|
if (span.kind === 'capability') {
|
|
46
46
|
const a = get(`${kindOf(span)}:${span.name}`);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* biome-ignore-all lint/style/noDescendingSpecificity: 同上:规则只比末段标签,.tr-meta / .tr-ev .tr-name 与 .tr-row.error .tr-meta 等的对手选择器结构上不相交(清单见 PR #84) */
|
|
2
|
+
|
|
1
3
|
/* Agentia trace 调用树样式 —— 从 packages/website/src/styles/global.css 的 .tr-* 段原样抽出。
|
|
2
4
|
* 宿主需提供这些 CSS 变量:--ice(accent)--faint(次要文本)--text(主文本)。
|
|
3
5
|
* 官网用站点调色板;CLI inspector 页面自带同名变量,视觉一致。 */
|
package/dist/inspector/view.js
CHANGED
|
@@ -176,16 +176,16 @@ export function createTraceView(rootEl, opts = {}) {
|
|
|
176
176
|
const cc = node.usage.cacheCreation || 0;
|
|
177
177
|
if (cr || cc) parts.push('cache ↑' + fmtNum(cr) + ' ↓' + fmtNum(cc));
|
|
178
178
|
}
|
|
179
|
-
if (bad) parts.push(
|
|
179
|
+
if (bad) parts.push(node.error?.type || 'error');
|
|
180
180
|
meta = parts.join(' · ');
|
|
181
181
|
} else meta = '…';
|
|
182
182
|
const metaEl = el('span', 'tr-meta', meta);
|
|
183
|
-
if (bad && node.error
|
|
183
|
+
if (bad && node.error?.message) metaEl.title = node.error.message;
|
|
184
184
|
row.appendChild(metaEl);
|
|
185
185
|
rootEl.appendChild(row);
|
|
186
186
|
// 失败原文必须**看得见** —— 只塞进 title(hover 才显)等于没显示。首次运行最常见的失败
|
|
187
187
|
// (没配 API key)就靠这一行定位;单起一行,不去挤占 nowrap 的 span 行。
|
|
188
|
-
if (bad && node.error
|
|
188
|
+
if (bad && node.error?.message) {
|
|
189
189
|
const errLine = el('div', 'tr-errm');
|
|
190
190
|
errLine.appendChild(el('span', 'tr-pre', branch + ' '));
|
|
191
191
|
errLine.appendChild(el('span', 'tr-errm-msg', node.error.message));
|
package/dist/inspector-page.html
CHANGED
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
.empty { color: var(--faint); padding: 16px; }
|
|
53
53
|
#ttl { color: var(--faint); margin-bottom: 10px; font-family: var(--mono); font-size: 11.5px; }
|
|
54
54
|
.usage { display: flex; gap: 16px; margin-top: 12px; color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
55
|
+
/* biome-ignore lint/style/noDescendingSpecificity: 本行只颜色/字重,对手是 33 行的 header .live b —— 两者祖先容器不同(.usage 在面板区、.live 在 header),结构上不可能命中同一元素,是规则「只比末段标签」的误报。 */
|
|
55
56
|
.usage b { color: var(--text); font-weight: 500; }
|
|
56
57
|
</style>
|
|
57
58
|
</head>
|
package/dist/inspector.js
CHANGED
|
@@ -59,7 +59,7 @@ function summarize(t) {
|
|
|
59
59
|
const startedAt = root ? root.startedAt : 0;
|
|
60
60
|
const endedAt = root && root.endedAt != null ? root.endedAt : startedAt;
|
|
61
61
|
const u = t.totalUsage || {};
|
|
62
|
-
const status = t.status ||
|
|
62
|
+
const status = t.status || root?.status || 'ok';
|
|
63
63
|
return {
|
|
64
64
|
traceId: t.traceId,
|
|
65
65
|
status,
|
package/dist/report.js
CHANGED
|
@@ -31,12 +31,16 @@ export function extractTrace(v) {
|
|
|
31
31
|
}
|
|
32
32
|
/** 打印一张按总耗时降序的能力排行(跨多行记录时按能力合并) */
|
|
33
33
|
/** 用法串(cli.ts 的子命令 `--help` 也从这里取,避免两处各写一份) */
|
|
34
|
-
export const USAGE = '用法:agentia report <trace.jsonl>';
|
|
34
|
+
export const USAGE = '用法:agentia report <trace.jsonl> [--json]';
|
|
35
35
|
export async function reportCommand(args) {
|
|
36
|
-
|
|
36
|
+
// `--json`:机器可读输出(stdout 只有一个 JSON 文档,无任何人类装饰)—— 便于脚本/CI 串接。
|
|
37
|
+
// 出错仍走 stderr 的 `错误:…` + 退出码 1、stdout 为空,脚本据此区分「有结果」与「没跑成」。
|
|
38
|
+
const json = args.includes('--json');
|
|
39
|
+
const positional = args.filter((a) => a !== '--json');
|
|
40
|
+
const file = positional[0];
|
|
37
41
|
// 失败一律**抛错**(而不是就地设 process.exitCode)—— 本命令是异步的,
|
|
38
42
|
// 就地设的 exitCode 会被 cli.ts 末尾那句 `process.exitCode = main(...)` 覆盖成 0。
|
|
39
|
-
if (file === undefined ||
|
|
43
|
+
if (file === undefined || positional.length > 1 || file.startsWith('--')) {
|
|
40
44
|
throw new Error(USAGE);
|
|
41
45
|
}
|
|
42
46
|
let raw;
|
|
@@ -101,37 +105,54 @@ export async function reportCommand(args) {
|
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
const rows = [...merged.values()].sort((a, b) => b.totalMs - a.totalMs || b.calls - a.calls);
|
|
108
|
+
const failRows = traces
|
|
109
|
+
.filter((t) => t.status === 'error')
|
|
110
|
+
.map((t) => {
|
|
111
|
+
const spans = (t.spans ?? []);
|
|
112
|
+
return {
|
|
113
|
+
traceId: t.traceId ?? null,
|
|
114
|
+
message: spans.find((s) => s?.error?.message)?.error?.message ?? null,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const totalMs = rows.reduce((n, r) => n + r.totalMs, 0);
|
|
118
|
+
const errTotal = rows.reduce((n, r) => n + r.errors, 0);
|
|
119
|
+
if (json) {
|
|
120
|
+
const payload = {
|
|
121
|
+
file,
|
|
122
|
+
runs: traces.length,
|
|
123
|
+
failed: traces.length - okRuns,
|
|
124
|
+
skippedLines: badLines,
|
|
125
|
+
failures: failRows,
|
|
126
|
+
capabilities: rows,
|
|
127
|
+
totals: { totalMs, errors: errTotal, capabilities: rows.length },
|
|
128
|
+
};
|
|
129
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
104
132
|
const pad = (s, n) => (s.length >= n ? s : s + ' '.repeat(n - s.length));
|
|
105
133
|
const fmtMs = (ms) => (ms >= 1000 ? `${(ms / 1000).toFixed(2)}s` : `${ms}ms`);
|
|
106
134
|
console.log(`trace 文件 ${file}`);
|
|
107
135
|
console.log(`runs ${traces.length}(失败 ${traces.length - okRuns})${badLines > 0 ? ` · 跳过无法解析 ${badLines} 行` : ''}`);
|
|
108
136
|
// 失败 run 要说清原因:只报「失败 N」而不给理由,对调优没用。而且首次运行最常见的失败
|
|
109
137
|
// (没配 API key)恰恰不产出任何 capability 行 —— 会走到下面「没有可归因的能力」提前返回。
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const spans = (t.spans ?? []);
|
|
113
|
-
const msg = spans.find((s) => s?.error?.message)?.error?.message;
|
|
114
|
-
console.log(` ✗ ${t.traceId ?? '(无 traceId)'}${msg ? ` ${msg}` : ''}`);
|
|
138
|
+
for (const f of failRows.slice(0, 3)) {
|
|
139
|
+
console.log(` ✗ ${f.traceId ?? '(无 traceId)'}${f.message ? ` ${f.message}` : ''}`);
|
|
115
140
|
}
|
|
116
|
-
if (
|
|
117
|
-
console.log(` …另有 ${
|
|
141
|
+
if (failRows.length > 3)
|
|
142
|
+
console.log(` …另有 ${failRows.length - 3} 条失败 run`);
|
|
118
143
|
console.log('');
|
|
119
144
|
if (rows.length === 0) {
|
|
120
145
|
console.log('(没有可归因的能力:这些 trace 里既没有 capability span,也没有 tool.output 事件)');
|
|
121
146
|
return 0;
|
|
122
147
|
}
|
|
123
148
|
console.log(`${pad('capability', 34)} ${pad('calls', 6)} ${pad('err', 5)} ${pad('total', 9)} ${pad('max', 9)} ${pad('tokens', 9)} ${pad('cost', 12)}`);
|
|
124
|
-
let total = 0;
|
|
125
|
-
let errs = 0;
|
|
126
149
|
for (const r of rows) {
|
|
127
|
-
total += r.totalMs;
|
|
128
|
-
errs += r.errors;
|
|
129
150
|
console.log(`${pad(r.capability, 34)} ${pad(String(r.calls), 6)} ${pad(String(r.errors), 5)} ` +
|
|
130
151
|
`${pad(fmtMs(r.totalMs), 9)} ${pad(fmtMs(r.maxMs), 9)} ` +
|
|
131
152
|
`${pad(r.tokens != null ? String(r.tokens) : '-', 9)} ` +
|
|
132
153
|
`${pad(r.costUsd != null ? r.costUsd.toFixed(6) : '-', 12)}`);
|
|
133
154
|
}
|
|
134
155
|
console.log('');
|
|
135
|
-
console.log(`合计耗时 ${fmtMs(
|
|
156
|
+
console.log(`合计耗时 ${fmtMs(totalMs)} · 失败 ${errTotal} 次 · 能力 ${rows.length} 个`);
|
|
136
157
|
return 0;
|
|
137
158
|
}
|