@migor/cli 0.10.6 → 0.10.8

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.
@@ -217,3 +217,159 @@
217
217
  word-break: break-word;
218
218
  min-width: 0;
219
219
  }
220
+
221
+ /* —— 结构化正文(2026-11-11):`full` 是对象/数组时,展开行**下方**多一个兄弟块 ——
222
+ 此前只有一条展开路:整串无缩进的 JSON 塞进 `.tr-io` 再靠 `.tr-open` 换行,20 个键就在
223
+ 400px 宽的右栏里折成十几行连续文本(页面不报错、无溢出,只是人读不了)。
224
+
225
+ 为什么是兄弟块、不是 `.tr-io` 的子节点:`.tr-row` 是 flex + nowrap,往里塞块级内容会把
226
+ 树的列对齐打乱;独立成块才能自己换行、自己缩进、自己滚。
227
+
228
+ 为什么 `.tr-io` 只是**隐藏**而不是不渲染:它仍持有原文(`textContent` = `JSON.stringify`),
229
+ `test/view.test.js` 的「展开拿到的是原文、不是摘要」与用户选中复制都靠它 ——
230
+ 这里改的是画法,不是数据。 */
231
+ .tr-row.tr-structured .tr-io {
232
+ display: none;
233
+ }
234
+ .tr-full {
235
+ margin: 2px 0 8px 2.2em; /* 与 `.tr-pre` 的树形前缀对齐,不跟父行抢左侧 */
236
+ border-left: 2px solid #2a2a31;
237
+ padding: 4px 0 4px 10px;
238
+ font-size: 11.5px;
239
+ line-height: 1.55;
240
+ /* 与 `.tr-row.tr-open .tr-io` 同一个高度上限:出参动辄上千行,不限高会把整棵树推出屏幕 */
241
+ max-height: 60vh;
242
+ overflow: auto;
243
+ white-space: normal;
244
+ }
245
+ .tr-full-head {
246
+ display: flex;
247
+ gap: 10px;
248
+ margin-bottom: 2px;
249
+ font-size: 10.5px;
250
+ color: var(--faint);
251
+ }
252
+ .tr-full-copy {
253
+ color: var(--ice);
254
+ cursor: pointer;
255
+ }
256
+ .tr-full-copy:hover {
257
+ text-decoration: underline;
258
+ }
259
+ /* 每一行自己换行:长字符串(工具出参常见)必须能折,否则横向滚动条把缩进吃掉 */
260
+ .tr-full-line {
261
+ white-space: pre-wrap;
262
+ overflow-wrap: anywhere;
263
+ border-radius: 3px;
264
+ }
265
+ /* 可折叠的行(有 data-fold)才给 hover 反馈 —— 叶子行点了没事发生,就不该有可点的样子 */
266
+ .tr-full-line[data-fold] {
267
+ cursor: pointer;
268
+ }
269
+ .tr-full-line[data-fold]:hover {
270
+ background: #17171a;
271
+ }
272
+ /* 折叠箭头与 .tr-caret 不同:它不是「这一行能展开」的唯一指示(同一行还写着 `{ object · 4 项 }`,
273
+ 折叠态的值本身就说明了一切),所以不做常显的对比度承诺,跟着 hover 走。
274
+ **定宽**:叶子行也占这一格(见 full-view.js 的 caretSlot),否则同层键名会在有无箭头之间
275
+ 左右跳 —— 一列键名对齐是这类面板最基本的可读性。 */
276
+ .tr-full-caret {
277
+ display: inline-block;
278
+ width: 10px;
279
+ text-align: center;
280
+ color: var(--faint);
281
+ font-size: 10px;
282
+ margin-right: 3px;
283
+ }
284
+ .tr-full-hint {
285
+ color: var(--faint);
286
+ }
287
+ /* 「这个字符串里装着 JSON」的标记。它不是装饰,是**事实的一部分**:没有它,读者会以为
288
+ 工具真的返回了对象。所以常显、不等 hover,也不跟 .tr-full-hint 一样隐形 —— 但保持
289
+ 低对比度,避免每行都在喊。 */
290
+ .tr-full-json {
291
+ color: var(--faint);
292
+ font-size: 10px;
293
+ line-height: 1.4;
294
+ border: 1px solid #2c2c32;
295
+ border-radius: 3px;
296
+ padding: 0 4px;
297
+ margin-right: 4px;
298
+ }
299
+ /* `⧉` 复制路径:hover 才露。与 .tr-caret 的区别是**它暴露的是动作、不是信息** ——
300
+ 路径的内容(键名)始终写在行里,常显只会给每行多一个字形噪声。
301
+ 没有剪贴板实现时照样显示、点了不谎报成功(见 full-view.js 的 copyInto)。 */
302
+ .tr-full-path {
303
+ opacity: 0;
304
+ color: var(--ice);
305
+ cursor: pointer;
306
+ font-size: 10px;
307
+ margin: 0 4px;
308
+ transition: opacity 0.12s;
309
+ }
310
+ .tr-full-line:hover .tr-full-path,
311
+ .tr-full-table th:hover .tr-full-path {
312
+ opacity: 1;
313
+ }
314
+ /* —— 同构对象数组 → 表格(2026-11-11 晚)——
315
+ 3 行 `{name,score}` 在树形里要花 9 行重复键名,成表后是 1 行表头 + 3 行数据,
316
+ 而且列天然对齐。只在「真同构且全标量列」时启用(判据在 full-view.js 的 tableColsOf),
317
+ 否则回落树形 —— 表格里没法逐层展开,判错一次就是丢数据。 */
318
+ .tr-full-table {
319
+ border-collapse: collapse;
320
+ margin: 2px 0 4px;
321
+ font-size: 11.5px;
322
+ }
323
+ /* 缩进:列数/行数都是动态的,没有能写死的 inline style(stub 里也没有 style 字段),
324
+ 所以按层级给属性选择器 —— 0 层不写 data-depth,自然不缩进。 */
325
+ .tr-full-table[data-depth="1"] {
326
+ margin-left: 2ch;
327
+ }
328
+ .tr-full-table[data-depth="2"] {
329
+ margin-left: 4ch;
330
+ }
331
+ .tr-full-table[data-depth="3"] {
332
+ margin-left: 6ch;
333
+ }
334
+ .tr-full-table[data-depth="4"] {
335
+ margin-left: 8ch;
336
+ }
337
+ .tr-full-table[data-depth="5"] {
338
+ margin-left: 10ch;
339
+ }
340
+ .tr-full-table th {
341
+ text-align: left;
342
+ font-weight: 500;
343
+ color: #71717a;
344
+ padding: 0 12px 1px 0;
345
+ border-bottom: 1px solid #27272a;
346
+ white-space: nowrap;
347
+ }
348
+ .tr-full-table td {
349
+ padding: 1px 12px 1px 0;
350
+ white-space: pre-wrap;
351
+ overflow-wrap: anywhere;
352
+ }
353
+ .tr-full-table tbody tr:hover {
354
+ background: #17171a;
355
+ }
356
+ /* 缩进用真空格 + `pre`,不靠 padding —— 嵌套层级要能被**选中复制**带出去 */
357
+ .tr-full-indent {
358
+ white-space: pre;
359
+ }
360
+ .tr-full-key {
361
+ color: #a1a1aa;
362
+ margin-right: 2px;
363
+ }
364
+ .tr-full-str {
365
+ color: #9ece6a;
366
+ }
367
+ .tr-full-num {
368
+ color: #e0af68;
369
+ }
370
+ .tr-full-bool {
371
+ color: #7dcfff;
372
+ }
373
+ .tr-full-null {
374
+ color: var(--faint);
375
+ }
@@ -17,6 +17,16 @@
17
17
  * 宿主需提供的 CSS 变量:--ice --faint --text(见 trace-view.css)。
18
18
  */
19
19
 
20
+ /* 展开态的正文有两种形状,由 `full` 的类型决定(2026-11-11,2026-11-12 扩了一档):
21
+ * - 纯文本字符串 → 老路:`.tr-io` 里放原文,靠 `.tr-open` 换行(纯文本正文、官网模拟回放);
22
+ * - 对象/数组 → 追加一个 `.tr-full` 结构化块(折叠 / 成表 / 路径复制),`.tr-io` 由 CSS 隐藏;
23
+ * - **装着 JSON 的字符串**(`content: "{\"rows\":[...]}"`,工具把结构序列化后塞回文本字段)
24
+ * → 同上一档,块头写明「string · N 字符 → object · M 项」并打 `str→json` 标记。
25
+ * 判据与块内完全同源(`hasStructuredBody`),「挂不挂块」与「块里画什么」因此不会跑偏。
26
+ * 前两档并存是刻意的:纯文本那条路有 test/style.test.js 的两个不变量守着(最小宽度、caret 常显),
27
+ * 全换成本件会让那两个守卫失去落点。 */
28
+ import { createFullView, hasStructuredBody } from './full-view.js';
29
+
20
30
  export const CAP_ICO = { tool: '⚙', skill: '◆', prompt: '¶', subagent: '⊕' };
21
31
 
22
32
  /** 能力类型取自名字前缀(tool: / skill: / prompt: / subagent:) */
@@ -88,6 +98,9 @@ export function createTraceView(rootEl, opts = {}) {
88
98
  /* 展开态的行 key 集合 + 事件 key 发号器。两者都必须活在 renderTrace 之外:
89
99
  渲染是【全量重建】(rootEl.innerHTML = ''),状态放进 DOM 或渲染过程里就活不过下一次事件。 */
90
100
  const expanded = new Set();
101
+ /* 结构化块内部的折叠状态,按事件键一份(`ev.key` → 路径 → 是否展开)。
102
+ 与 `expanded` 同一条理由,而且更硬:块本身每次重建都会 new 一个,状态只能挂在树上。 */
103
+ const folds = new Map();
91
104
  let evSeq = 0;
92
105
 
93
106
  /** 正在选文本时不要收起/展开 —— 鼠标拖选到行外松手会补一次 click,否则选完就自己合上了 */
@@ -136,20 +149,36 @@ export function createTraceView(rootEl, opts = {}) {
136
149
  // 非 tool.* 事件(usage.unpriced / llm.retry / budget.* / context.budget)没有工具名 ——
137
150
  // 这一格整个不渲染,别把「无工具」显示成假工具名(.tr-name 是 flex:none,.tr-io 自然占满)
138
151
  if (ev.tool) row.appendChild(el('span', 'tr-name', ev.tool));
139
- // 展开态用全文(ev.full),折叠态用摘要(ev.text)—— 折叠态因此与加展开之前逐字一致
140
- const io = el('span', 'tr-io', (open ? ev.full : ev.text) || '');
152
+ // 展开态用全文(ev.full),折叠态用摘要(ev.text)—— 折叠态因此与加展开之前逐字一致。
153
+ // `full` 是对象/数组、或**装着 JSON 的字符串**时(2026-11-11 / 2026-11-12):`.tr-io`
154
+ // 仍放**原文**(结构化块挂在行后、由 CSS 隐藏本格),所以「展开拿到的是原文而不是
155
+ // 62 字符摘要」这条不变量原样成立。
156
+ const structured = hasStructuredBody(ev.full);
157
+ const fullText = structured ? rawArg(ev.full) : ev.full || '';
158
+ const io = el('span', 'tr-io', (open ? fullText : ev.text) || '');
141
159
  // 展开后正文已在行里,再挂一个占满整行的原生 tooltip 只会挡视线
142
160
  io.title = open ? '' : ev.text || '';
143
161
  row.appendChild(io);
144
162
  if (ev.full) {
145
163
  row.dataset.expandable = '1'; // 可展开标记:CSS 靠它出 caret / cursor,测试靠它找行
146
164
  row.appendChild(el('span', 'tr-caret', open ? '▾' : '▸'));
147
- if (open) row.className += ' tr-open';
165
+ if (open) row.className += ' tr-open' + (structured ? ' tr-structured' : '');
148
166
  row.addEventListener('click', () => {
149
167
  if (!hasSelection()) toggleExpand(ev.key);
150
168
  });
151
169
  }
152
170
  rootEl.appendChild(row);
171
+ // 结构化正文是**兄弟块**而不是 `.tr-io` 的子节点:行是 flex + nowrap,
172
+ // 塞进去会把树的列对齐打乱;独立成块才能自己换行、自己缩进。
173
+ // 折叠状态按事件键取一份带进去(2026-11-11 晚):块每次重建都 new,状态不能放块里。
174
+ if (open && structured) {
175
+ let fold = folds.get(ev.key);
176
+ if (!fold) {
177
+ fold = new Map();
178
+ folds.set(ev.key, fold);
179
+ }
180
+ rootEl.appendChild(createFullView(ev.full, { fold }));
181
+ }
153
182
  return;
154
183
  }
155
184
 
@@ -224,6 +253,7 @@ export function createTraceView(rootEl, opts = {}) {
224
253
  spanMap.clear();
225
254
  spanMap.set('root', traceRoot);
226
255
  expanded.clear(); // 展开态属于上一棵树:reset 开新树时一并清掉
256
+ folds.clear(); // 块内折叠同理:留着只会在下一次点开时出现"莫名已折叠"的状态
227
257
  usageAcc.input = 0;
228
258
  usageAcc.output = 0;
229
259
  usageAcc.cacheRead = 0;
@@ -108,6 +108,10 @@ const STATIC = new Map([
108
108
  ['index.js', join(ASSETS, 'index.js')],
109
109
  ['view.js', join(ASSETS, 'view.js')],
110
110
  ['fromTrace.js', join(ASSETS, 'fromTrace.js')],
111
+ /* 结构化展开正文(2026-11-11):`full` 是对象/数组时,view.js 用它铺缩进/着色/复制块。
112
+ * 与上面四个同性质 —— 渲染器的一部分,页面**会** import 它(view.js 的静态 import 被
113
+ * 剥成页面级 import 时它必须可达),漏登记 = 浏览器 404 = 面板整块模块求值失败。 */
114
+ ['full-view.js', join(ASSETS, 'full-view.js')],
111
115
  ['summary.js', join(ASSETS, 'summary.js')],
112
116
  ['trace-view.css', join(ASSETS, 'trace-view.css')],
113
117
  /* 单文件 bundle(`agentia view` 内联进自包含 HTML 用)。⚠️ **页面不 import 它** ——
@@ -11,10 +11,10 @@
11
11
  "typecheck": "tsc --noEmit -p tsconfig.json"
12
12
  },
13
13
  "dependencies": {
14
- "@migor/agentia": "^0.10.6"
14
+ "@migor/agentia": "^0.10.8"
15
15
  },
16
16
  "devDependencies": {
17
- "@migor/cli": "^0.10.6",
17
+ "@migor/cli": "^0.10.8",
18
18
  "tsx": "^4.19.0",
19
19
  "typescript": "^7.0.2",
20
20
  "@types/node": "^22.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@migor/cli",
3
- "version": "0.10.6",
3
+ "version": "0.10.8",
4
4
  "description": "CLI for the Agentia framework: scaffold a project, generate a capability, and open a local trace inspector for runs.",
5
5
  "type": "module",
6
6
  "bin": {