@migor/cli 0.3.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.
- package/dist/AGENTS.md +20 -2
- package/dist/inspector/fromTrace.js +15 -1
- package/dist/inspector/index.js +9 -1
- package/dist/inspector/trace-view.css +51 -0
- package/dist/inspector/view.js +65 -5
- package/dist/templates.js +1 -1
- package/package.json +1 -1
package/dist/AGENTS.md
CHANGED
|
@@ -162,6 +162,7 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
162
162
|
| `onUnpricedModel` | 遇到价格表外的模型时回调(`{ model, spanId }`,每个循环作用域内每模型一次);抛错被吞,**不改变 run 结局**(定价缺失是宿主配置问题)。用它接告警 |
|
|
163
163
|
| `toolTimeoutMs` | 缺省单工具超时(毫秒);超时该条 tool_result 记 is_error,不杀 run |
|
|
164
164
|
| `maxToolConcurrency` | 缺省同回合并行工具上限;不设 = 不限(全并行) |
|
|
165
|
+
| `maxEventChars` | 缺省 trace 事件正文截断上限(可被单次 run 覆盖):数字 = 入参/出参统一用该上限,`false` = **不截断**(完整正文进 trace,面板里能展开看全文);不设 = 框架缺省 |
|
|
165
166
|
|
|
166
167
|
### `app.run(messages, opts?: RunAppOptions)`
|
|
167
168
|
|
|
@@ -185,6 +186,7 @@ npx @migor/cli doctor # 静态体检(未登记/悬空/命名/重复
|
|
|
185
186
|
| `onUnpricedModel` | 单次覆盖未定价回调(**是函数,因此不在 transport 的 `RunInvocationOptions` 里** —— 异步宿主不会替你传) |
|
|
186
187
|
| `toolTimeoutMs` | 单个工具执行超时(毫秒);超时该条 tool_result 记 is_error,run 继续 |
|
|
187
188
|
| `maxToolConcurrency` | 同回合并行工具上限;缺省不限 |
|
|
189
|
+
| `maxEventChars` | trace 事件正文截断上限(字符):数字 = 入参/出参统一用该上限,`false` = **不截断**;缺省按类型收敛(入参/成功出参 2000、失败出参 1000)。**透传给子 agent/skill 的子循环** —— 同一棵调用树上口径一致。只影响**记账**,回给模型的 tool_result 永远完整 |
|
|
188
190
|
| `session` | 会话持久化 `{ store, id }`:run 前拼历史、成功收尾追加本轮(见 `SessionStore`) |
|
|
189
191
|
|
|
190
192
|
返回 `AgentRunOutput`:`{ run, result }`。`result` 含 `trace` / `stopReason` / `finalText` / `iterations` / `error` / `typed`。
|
|
@@ -613,6 +615,18 @@ if (!report.ok) console.error(report.cases.filter((c) => !c.ok));
|
|
|
613
615
|
- **内存上限** ≈ `(1 + 能力数 + 模型数) × windowSize` —— 能力数由 `maxCapabilities` 封顶,长跑宿主不会被拖住。
|
|
614
616
|
- `costUsd` 依赖模型在价格表内(不在表里时不计、并计入 `unpricedTurns` 与 `usage.unpriced` 事件);根 span 未收尾(如失败路径的半截 trace)的 run 不进延迟样本。
|
|
615
617
|
|
|
618
|
+
### 调用树面板(`agentia dev` 的本地面板 / 官网 Playground)
|
|
619
|
+
|
|
620
|
+
同一份 `@migor/trace-view` 渲染器,**面板 / Playground / `report` 的能力排行三处共用**,不各写一套。
|
|
621
|
+
|
|
622
|
+
- **折叠态是「一行一件事」**:事件行(`tool.input` / `tool.output`)只显示省略号收敛的摘要。
|
|
623
|
+
- **点事件行展开看完整正文**(正文可选中复制),再点收起。展开的正文**默认仍与标签同一行**,
|
|
624
|
+
只有在放不下时才整段换到下一行 —— 官网 Playground 的 trace 列(约 320px)就属于放不下。
|
|
625
|
+
- **可展开的行右端常显一个 caret**(`▸` / `▾`):这一行能不能展开随时看得见,不必先悬停才发现。
|
|
626
|
+
- **展开只能展开 trace 里存着的正文** —— 想看到被截断掉的部分,得在**记账时**就别截:`maxEventChars: false`(见 §4)。缺省截到 2000 字符,展开了也只有那 2000 字符。
|
|
627
|
+
- 入参折叠态的摘要被砍到 62 字符(4 个键 / 每值 21 字符);**展开拿到的是原文**,不是那份摘要 ——
|
|
628
|
+
本地面板与官网 Playground 两个宿主都是如此(两个宿主都不该只喂摘要,否则点开什么都没多出来)。
|
|
629
|
+
|
|
616
630
|
### 调优报告(**哪个能力慢 / 贵 / 爱失败**)
|
|
617
631
|
|
|
618
632
|
指标回答「整体怎么样」,报告回答「**该拧哪个旋钮**」:
|
|
@@ -659,9 +673,13 @@ const app = await createApp({ /* … */ sinks: [jsonl] });
|
|
|
659
673
|
### 生效配置快照(「这条 run 用了哪套旋钮」)
|
|
660
674
|
|
|
661
675
|
每个 run 的**根 span** 都带一组 `config.*` attributes(`config.maxTokens` / `config.maxCostUsd` /
|
|
662
|
-
`config.retry.maxAttempts` / `config.contextPolicy.budgetTokens` / `config.priceOverrides`
|
|
663
|
-
|
|
676
|
+
`config.retry.maxAttempts` / `config.contextPolicy.budgetTokens` / `config.priceOverrides` /
|
|
677
|
+
`config.maxEventChars` …)。
|
|
678
|
+
带缺省值的那几项(`maxTokens` / `maxIterations` / `contextPolicy` / `retry`)**缺省值也记**——
|
|
679
|
+
「没配」与「配了缺省值」因此可区分;可选项(`toolTimeoutMs` / `maxToolConcurrency` /
|
|
680
|
+
`maxEventChars`)只在设了才记。换参数前后对比、复现线上行为都有据可查。
|
|
664
681
|
函数型选项(`summarize` / `confirm` 之类)只记「配没配」,不记函数体。
|
|
682
|
+
截断关掉时记的是 `'off'` 而不是 `false` —— 后者在日志/看板里会被读成「上限为 0」。
|
|
665
683
|
|
|
666
684
|
### 提示词版本化
|
|
667
685
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 3. usage 只由 llm.turn 累计(capability span 的 usage 是子 span 聚合,计入会双算)。
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { fmtArg, capabilityTypeOf, CAP_ICO } from './view.js';
|
|
17
|
+
import { fmtArg, rawArg, capabilityTypeOf, CAP_ICO } from './view.js';
|
|
18
18
|
|
|
19
19
|
/** 从 span.attributes 认能力类型(框架用 `setAttribute(capabilityId, 'subagent', name)` 记类型) */
|
|
20
20
|
function spanType(s) {
|
|
@@ -67,6 +67,19 @@ function eventText(ev) {
|
|
|
67
67
|
return body == null ? '' : typeof body === 'string' ? body : JSON.stringify(body);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 事件全文(展开态用)。
|
|
72
|
+
*
|
|
73
|
+
* 只有 `tool.input` 需要单独取:`fmtArg` 把入参摘要**砍到 4 个键 / 每值 21 字符 / 整串 62 字符**,
|
|
74
|
+
* 直接拿摘要当全文就是「假展开」—— 点开看到的还是那 62 个字符。出参与其他事件本身就是原文
|
|
75
|
+
* (`eventText` 原样返回 content),无需二次处理。
|
|
76
|
+
* 原文口径统一在 view.js 的 `rawArg`:官网 playground 的模拟回放也要用它,两处一处定义。
|
|
77
|
+
*/
|
|
78
|
+
function eventFull(ev) {
|
|
79
|
+
if (ev.name === 'tool.input') return rawArg(ev.body && ev.body.input);
|
|
80
|
+
return eventText(ev);
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
/**
|
|
71
84
|
* 把真实 Trace 线性回放给视图(同步、一次性;用于 run 结束后的静态展示)。
|
|
72
85
|
* 返回 false 表示 trace 空、未做任何渲染。
|
|
@@ -126,6 +139,7 @@ export function playTrace(view, trace) {
|
|
|
126
139
|
eventToolName(it.ev.name, it.ev.body && it.ev.body.tool, typeMap),
|
|
127
140
|
eventText(it.ev),
|
|
128
141
|
!it.ev.body || it.ev.body.ok !== false,
|
|
142
|
+
eventFull(it.ev),
|
|
129
143
|
);
|
|
130
144
|
} else {
|
|
131
145
|
view.end({
|
package/dist/inspector/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/* @migor/trace-view —— Agentia trace 调用树渲染器(框架无关、零依赖)。
|
|
2
2
|
* 官网 playground 与 CLI inspector 共用同一份,避免两处渲染漂移。 */
|
|
3
3
|
|
|
4
|
-
export {
|
|
4
|
+
export {
|
|
5
|
+
createTraceView,
|
|
6
|
+
capabilityTypeOf,
|
|
7
|
+
CAP_ICO,
|
|
8
|
+
fmtArg,
|
|
9
|
+
rawArg,
|
|
10
|
+
fmtNum,
|
|
11
|
+
fmtMs,
|
|
12
|
+
} from './view.js';
|
|
5
13
|
export { playTrace } from './fromTrace.js';
|
|
6
14
|
export { summarizeTrace, renderSummary } from './summary.js';
|
|
@@ -104,6 +104,57 @@
|
|
|
104
104
|
color: #f7768e;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/* —— 行展开:折叠时「一行一个语义单元」,点开的那一行改为显示**完整正文** ——
|
|
108
|
+
caret 绝对定位在行右端,不占 flex 宽度,所以行内其它元素的排版不受它影响。
|
|
109
|
+
**常显,并且是满不透明度的 `--faint`**:它是个**交互指示器**,不是装饰 ——
|
|
110
|
+
caret 用 0.75 的透明度试过,量出来是缺陷而非口味:WCAG 1.4.11 对非文本 UI 组件
|
|
111
|
+
要求 ≥3:1,而 0.75 的 `--faint`(#71717a) 在官网 #0c0c0e 面板上实际绘制成 #58585f,
|
|
112
|
+
只有 **2.77:1**(满不透明度 4.04:1)。
|
|
113
|
+
所以没有「hover 才变亮」这一档:静止时它已经是最亮的那一档了。 */
|
|
114
|
+
.tr-row[data-expandable] {
|
|
115
|
+
position: relative;
|
|
116
|
+
cursor: pointer;
|
|
117
|
+
}
|
|
118
|
+
.tr-caret {
|
|
119
|
+
position: absolute;
|
|
120
|
+
right: 0;
|
|
121
|
+
top: 3px; /* 对齐 .tr-row 的 padding: 3px 0 */
|
|
122
|
+
color: var(--faint);
|
|
123
|
+
font-size: 10px;
|
|
124
|
+
}
|
|
125
|
+
/* caret 常显 ⇒ 右端要给它留一条窄槽。不留的话,被截断的正文省略号「…」正好落在 caret 底下,
|
|
126
|
+
▸ 与 … 糊成一个字形。给正文加右内边距 = 内容盒右缘左移,省略号自行退到槽的左边。 */
|
|
127
|
+
.tr-row[data-expandable] .tr-io {
|
|
128
|
+
padding-right: 12px;
|
|
129
|
+
}
|
|
130
|
+
/* 只有点开的那一行换行;树的其余部分保持 nowrap 的列对齐 */
|
|
131
|
+
.tr-row.tr-open {
|
|
132
|
+
white-space: normal;
|
|
133
|
+
align-items: flex-start;
|
|
134
|
+
/* 允许正文换到下一行 —— 但**只在它真的放不下时**(判据是下面那条 min-width,不是无条件换行) */
|
|
135
|
+
flex-wrap: wrap;
|
|
136
|
+
}
|
|
137
|
+
/* 展开的正文**有高度上限**:工具出参动辄上千行(实测一条 60 行的出参就有 17878px 的
|
|
138
|
+
scrollWidth),不限高会把整个调用树推到屏幕之外,「看下一行」比「看全文」更常用。
|
|
139
|
+
超出部分在行内滚动 —— 正文仍在 DOM 里,选中复制不受影响。 */
|
|
140
|
+
.tr-row.tr-open .tr-io {
|
|
141
|
+
/* 正文与标签**同一行**(既定的阅读节奏:一行一个语义单元)。换行只发生在**放不下时**,
|
|
142
|
+
而这条线由 min-width 划 —— 所以 flex-basis 必须是 0:
|
|
143
|
+
· basis: auto ⇒ 换行判据是 max-content(正文动辄上百字符 ⇒ 几乎必然换行,等于无条件换行);
|
|
144
|
+
· basis: 0 ⇒ 判据回到 min-width,放得下就同排、放不下才整段换行。
|
|
145
|
+
为什么 min-width 不能省:正文用 overflow-wrap: anywhere,min-content 只有 1 个字符宽 ——
|
|
146
|
+
没有它时 flex 会心安理得地把正文压成一条 16px 的窄缝(59 个字符挤成 34 行、行高顶到上限),
|
|
147
|
+
而页面 scrollWidth === clientWidth 一切「干净」,只是读不了。
|
|
148
|
+
窄面板(官网 Playground 296px)里那行固定项本身就要 ~246px,所以那里它会如实地换行。 */
|
|
149
|
+
flex-basis: 0;
|
|
150
|
+
min-width: 22ch;
|
|
151
|
+
white-space: pre-wrap;
|
|
152
|
+
overflow-wrap: anywhere;
|
|
153
|
+
max-height: 60vh;
|
|
154
|
+
overflow: auto;
|
|
155
|
+
text-overflow: clip;
|
|
156
|
+
}
|
|
157
|
+
|
|
107
158
|
@keyframes tr-blink {
|
|
108
159
|
0%,
|
|
109
160
|
100% {
|
package/dist/inspector/view.js
CHANGED
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
* 这个先后本身就是语义,不能拍平成一类;
|
|
10
10
|
* - 四类能力标识符(⚙ tool / ◆ skill / ¶ prompt / ⊕ subagent)只靠字形区分、不上类型色。
|
|
11
11
|
*
|
|
12
|
+
* 展开:折叠态**一字不变**(省略号收敛,事件行比 span 行更轻),点事件行可展开看**完整正文**。
|
|
13
|
+
* 展开状态存在渲染之外(每次事件都会全量重建 DOM),否则实时 run 里刚点开的行会在下一条事件
|
|
14
|
+
* 到来时自己合上。数据侧「行内摘要」与「展开全文」是两个字段:`text` 是摘要(入参经 fmtArg
|
|
15
|
+
* 砍到 62 字符),`full` 才是原文 —— 只有全文能撑起「调试时看工具结果」这件事。
|
|
16
|
+
*
|
|
12
17
|
* 宿主需提供的 CSS 变量:--ice --faint --text(见 trace-view.css)。
|
|
13
18
|
*/
|
|
14
19
|
|
|
@@ -46,6 +51,19 @@ export function fmtArg(v) {
|
|
|
46
51
|
return s.length > 62 ? s.slice(0, 61) + '…' : s;
|
|
47
52
|
}
|
|
48
53
|
|
|
54
|
+
/* span 入参**原文** —— fmtArg 的对偶:摘要给折叠态,原文给展开态。
|
|
55
|
+
* 两者必须定义在同一处:哪个宿主自己搓一个「原文」,「展开」就会在那里退回摘要
|
|
56
|
+
* (点开什么都没多出来),而两个宿主共用这一份渲染器,正是为了不让这种漂移发生。 */
|
|
57
|
+
export function rawArg(v) {
|
|
58
|
+
if (v == null) return '';
|
|
59
|
+
if (typeof v === 'string') return v;
|
|
60
|
+
try {
|
|
61
|
+
return JSON.stringify(v) ?? String(v);
|
|
62
|
+
} catch {
|
|
63
|
+
return String(v);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
function el(tag, cls, text) {
|
|
50
68
|
const node = document.createElement(tag);
|
|
51
69
|
if (cls) node.className = cls;
|
|
@@ -67,6 +85,25 @@ export function createTraceView(rootEl, opts = {}) {
|
|
|
67
85
|
let traceRoot = null;
|
|
68
86
|
const spanMap = new Map();
|
|
69
87
|
const usageAcc = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0 };
|
|
88
|
+
/* 展开态的行 key 集合 + 事件 key 发号器。两者都必须活在 renderTrace 之外:
|
|
89
|
+
渲染是【全量重建】(rootEl.innerHTML = ''),状态放进 DOM 或渲染过程里就活不过下一次事件。 */
|
|
90
|
+
const expanded = new Set();
|
|
91
|
+
let evSeq = 0;
|
|
92
|
+
|
|
93
|
+
/** 正在选文本时不要收起/展开 —— 鼠标拖选到行外松手会补一次 click,否则选完就自己合上了 */
|
|
94
|
+
function hasSelection() {
|
|
95
|
+
try {
|
|
96
|
+
return String(globalThis.getSelection?.() ?? '') !== '';
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function toggleExpand(key) {
|
|
103
|
+
if (expanded.has(key)) expanded.delete(key);
|
|
104
|
+
else expanded.add(key);
|
|
105
|
+
renderTrace();
|
|
106
|
+
}
|
|
70
107
|
|
|
71
108
|
function renderTrace() {
|
|
72
109
|
rootEl.innerHTML = '';
|
|
@@ -89,6 +126,7 @@ export function createTraceView(rootEl, opts = {}) {
|
|
|
89
126
|
|
|
90
127
|
/* 事件行:没有 status 圈、没有耗时、不参与 usage 计数,只带入参/出参摘要 */
|
|
91
128
|
if (ev) {
|
|
129
|
+
const open = expanded.has(ev.key);
|
|
92
130
|
const row = el('div', 'tr-row tr-ev' + (ev.ok === false ? ' error' : ''));
|
|
93
131
|
row.dataset.ev = ev.type;
|
|
94
132
|
row.dataset.capability = capabilityTypeOf(ev.tool);
|
|
@@ -98,9 +136,19 @@ export function createTraceView(rootEl, opts = {}) {
|
|
|
98
136
|
// 非 tool.* 事件(usage.unpriced / llm.retry / budget.* / context.budget)没有工具名 ——
|
|
99
137
|
// 这一格整个不渲染,别把「无工具」显示成假工具名(.tr-name 是 flex:none,.tr-io 自然占满)
|
|
100
138
|
if (ev.tool) row.appendChild(el('span', 'tr-name', ev.tool));
|
|
101
|
-
|
|
102
|
-
io
|
|
139
|
+
// 展开态用全文(ev.full),折叠态用摘要(ev.text)—— 折叠态因此与加展开之前逐字一致
|
|
140
|
+
const io = el('span', 'tr-io', (open ? ev.full : ev.text) || '');
|
|
141
|
+
// 展开后正文已在行里,再挂一个占满整行的原生 tooltip 只会挡视线
|
|
142
|
+
io.title = open ? '' : ev.text || '';
|
|
103
143
|
row.appendChild(io);
|
|
144
|
+
if (ev.full) {
|
|
145
|
+
row.dataset.expandable = '1'; // 可展开标记:CSS 靠它出 caret / cursor,测试靠它找行
|
|
146
|
+
row.appendChild(el('span', 'tr-caret', open ? '▾' : '▸'));
|
|
147
|
+
if (open) row.className += ' tr-open';
|
|
148
|
+
row.addEventListener('click', () => {
|
|
149
|
+
if (!hasSelection()) toggleExpand(ev.key);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
104
152
|
rootEl.appendChild(row);
|
|
105
153
|
return;
|
|
106
154
|
}
|
|
@@ -175,6 +223,7 @@ export function createTraceView(rootEl, opts = {}) {
|
|
|
175
223
|
};
|
|
176
224
|
spanMap.clear();
|
|
177
225
|
spanMap.set('root', traceRoot);
|
|
226
|
+
expanded.clear(); // 展开态属于上一棵树:reset 开新树时一并清掉
|
|
178
227
|
usageAcc.input = 0;
|
|
179
228
|
usageAcc.output = 0;
|
|
180
229
|
usageAcc.cacheRead = 0;
|
|
@@ -206,11 +255,22 @@ export function createTraceView(rootEl, opts = {}) {
|
|
|
206
255
|
|
|
207
256
|
/* span 事件:框架把普通工具 / @Prompt 调用记成【turn 上的事件】,不给它们建 capability span
|
|
208
257
|
(只有 skill / subagent 会 recorder.begin('capability', …),见 engine/loop.ts)。
|
|
209
|
-
events 与 order 并存:events 是数据、order 负责与子 span 的先后顺序。
|
|
210
|
-
|
|
258
|
+
events 与 order 并存:events 是数据、order 负责与子 span 的先后顺序。
|
|
259
|
+
|
|
260
|
+
`text` = 行内摘要,`full` = 展开看的原文(省略时同 text —— 出参与多数事件本来就是原文,
|
|
261
|
+
只有 tool.input 的摘要被 fmtArg 砍过,调用方需要显式给 full)。
|
|
262
|
+
`key` 是展开状态的稳定标识:事件对象跨全量重建存活,所以键也稳定。 */
|
|
263
|
+
function event(id, type, tool, text, ok, full) {
|
|
211
264
|
const node = spanMap.get(id);
|
|
212
265
|
if (!node) return;
|
|
213
|
-
const e = {
|
|
266
|
+
const e = {
|
|
267
|
+
type,
|
|
268
|
+
tool,
|
|
269
|
+
text,
|
|
270
|
+
ok: ok !== false,
|
|
271
|
+
full: full === undefined ? text : full,
|
|
272
|
+
key: 'ev' + ++evSeq,
|
|
273
|
+
};
|
|
214
274
|
node.events.push(e);
|
|
215
275
|
node.order.push({ t: 'ev', e });
|
|
216
276
|
renderTrace();
|
package/dist/templates.js
CHANGED
|
@@ -60,7 +60,7 @@ export function projectPackageJson(name) {
|
|
|
60
60
|
dev: 'tsx src/main.ts',
|
|
61
61
|
typecheck: 'tsc --noEmit -p tsconfig.json',
|
|
62
62
|
},
|
|
63
|
-
dependencies: { '@migor/agentia': '^0.
|
|
63
|
+
dependencies: { '@migor/agentia': '^0.4.0' },
|
|
64
64
|
devDependencies: {
|
|
65
65
|
tsx: '^4.19.0',
|
|
66
66
|
typescript: '^7.0.2',
|