@yxzpro/agent-acta 2.14.4

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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/LOGFORMATS.md +960 -0
  3. package/README.md +238 -0
  4. package/REFERENCE.md +1428 -0
  5. package/agent-acta-page.html +1998 -0
  6. package/agent-acta-server.mjs +1066 -0
  7. package/agent-acta-widget.html +724 -0
  8. package/archive.mjs +335 -0
  9. package/core/log.mjs +20 -0
  10. package/core/service.mjs +4285 -0
  11. package/hooks/cursor-usage-hook.mjs +89 -0
  12. package/mcp-server.mjs +372 -0
  13. package/package.json +68 -0
  14. package/page/dialog-add-agent.js +65 -0
  15. package/page/dialog-alert.js +159 -0
  16. package/page/dialog-archive.js +342 -0
  17. package/page/dialog-compare.js +238 -0
  18. package/page/dialog-ctxw.js +101 -0
  19. package/page/dialog-diag.js +72 -0
  20. package/page/dialog-heatmap.js +215 -0
  21. package/page/dialog-selftest.js +202 -0
  22. package/page/dialog-sess-timeline.js +108 -0
  23. package/page/dialog-stats.js +694 -0
  24. package/page/dialog-usage.js +158 -0
  25. package/page/log-card.js +277 -0
  26. package/page/log-view.js +273 -0
  27. package/page/sess-view.js +382 -0
  28. package/page/shared.js +408 -0
  29. package/page/sidebar.js +129 -0
  30. package/page/style.css +973 -0
  31. package/page/topbar.js +61 -0
  32. package/parsers/atomcode.mjs +321 -0
  33. package/parsers/buddy.mjs +272 -0
  34. package/parsers/buddyext.mjs +582 -0
  35. package/parsers/claude.mjs +496 -0
  36. package/parsers/cline.mjs +468 -0
  37. package/parsers/codearts.mjs +96 -0
  38. package/parsers/codex.mjs +188 -0
  39. package/parsers/comate.mjs +195 -0
  40. package/parsers/copilot.mjs +308 -0
  41. package/parsers/cursor.mjs +493 -0
  42. package/parsers/devindb.mjs +358 -0
  43. package/parsers/discovery.mjs +1230 -0
  44. package/parsers/doubao.mjs +406 -0
  45. package/parsers/dsh.mjs +377 -0
  46. package/parsers/gemini.mjs +267 -0
  47. package/parsers/generic.mjs +494 -0
  48. package/parsers/hermes.mjs +398 -0
  49. package/parsers/kilo.mjs +42 -0
  50. package/parsers/kimi.mjs +249 -0
  51. package/parsers/mimocode.mjs +531 -0
  52. package/parsers/minimax.mjs +503 -0
  53. package/parsers/openclaw.mjs +490 -0
  54. package/parsers/opencode.mjs +465 -0
  55. package/parsers/shared.mjs +418 -0
  56. package/parsers/trace.mjs +66 -0
  57. package/parsers/tracecode.mjs +348 -0
  58. package/parsers/traedb.mjs +528 -0
  59. package/parsers/zcode.mjs +356 -0
  60. package/plugin/client.js +51 -0
  61. package/plugin/cordis.patch.yml +5 -0
  62. package/plugin/plugin.mjs +220 -0
  63. package/search.mjs +554 -0
  64. package/tools/grab-trae-key.ps1 +265 -0
  65. package/vendor/element-plus-icons.js +3 -0
  66. package/vendor/element-plus.css +1 -0
  67. package/vendor/element-plus.dark.css +1 -0
  68. package/vendor/element-plus.full.min.js +65 -0
  69. package/vendor/element-plus.zh-cn.js +2 -0
  70. package/vendor/svg/antgroup-color.svg +1 -0
  71. package/vendor/svg/bytedance-color.svg +1 -0
  72. package/vendor/svg/chatglm-color.svg +1 -0
  73. package/vendor/svg/claude-color.svg +1 -0
  74. package/vendor/svg/claudecode-color.svg +1 -0
  75. package/vendor/svg/cline.svg +1 -0
  76. package/vendor/svg/codebuddy-color.svg +1 -0
  77. package/vendor/svg/codebuddy.svg +1 -0
  78. package/vendor/svg/codex-color.svg +1 -0
  79. package/vendor/svg/copilot.svg +1 -0
  80. package/vendor/svg/cursor.svg +1 -0
  81. package/vendor/svg/deepseek-color.svg +1 -0
  82. package/vendor/svg/deepseek-harness-whale.svg +8 -0
  83. package/vendor/svg/doubao-color.svg +1 -0
  84. package/vendor/svg/gemini-color.svg +1 -0
  85. package/vendor/svg/glmv-color.svg +1 -0
  86. package/vendor/svg/grok.svg +1 -0
  87. package/vendor/svg/hermesagent.svg +1 -0
  88. package/vendor/svg/huawei-color.svg +1 -0
  89. package/vendor/svg/hunyuan-color.svg +1 -0
  90. package/vendor/svg/kilocode-avatar.svg +1 -0
  91. package/vendor/svg/kilocode.svg +1 -0
  92. package/vendor/svg/kimi-color.svg +1 -0
  93. package/vendor/svg/kimi.svg +1 -0
  94. package/vendor/svg/logo-AtomGit-G-red.svg +3 -0
  95. package/vendor/svg/logo-AtomGit-dark.svg +4 -0
  96. package/vendor/svg/logo-AtomGit-light.svg +4 -0
  97. package/vendor/svg/meta-color.svg +1 -0
  98. package/vendor/svg/mimo.svg +66 -0
  99. package/vendor/svg/minimax-code-logo.svg +8 -0
  100. package/vendor/svg/minimax-color.svg +1 -0
  101. package/vendor/svg/moonshot.svg +1 -0
  102. package/vendor/svg/nex-color.svg +1 -0
  103. package/vendor/svg/nex.svg +1 -0
  104. package/vendor/svg/nvidia-color.svg +1 -0
  105. package/vendor/svg/openai.svg +1 -0
  106. package/vendor/svg/openclaw-color.svg +1 -0
  107. package/vendor/svg/openclaw.svg +1 -0
  108. package/vendor/svg/opencode.svg +1 -0
  109. package/vendor/svg/openrouter-color.svg +1 -0
  110. package/vendor/svg/qoder-color.svg +1 -0
  111. package/vendor/svg/qwen-color.svg +1 -0
  112. package/vendor/svg/sensenova-color.svg +1 -0
  113. package/vendor/svg/stepfun.svg +1 -0
  114. package/vendor/svg/trae-color.svg +1 -0
  115. package/vendor/svg/traework-v2.svg +1 -0
  116. package/vendor/svg/wenxin-color.svg +1 -0
  117. package/vendor/svg/windsurf.svg +1 -0
  118. package/vendor/svg/workbuddy-color.svg +29 -0
  119. package/vendor/svg/xiaomimimo.svg +1 -0
  120. package/vendor/svg/yuanbao-color.svg +1 -0
  121. package/vendor/svg/yuanbao.svg +1 -0
  122. package/vendor/svg/zai.svg +1 -0
  123. package/vendor/svg/zhipu-color.svg +1 -0
  124. package/vendor/vue.global.prod.js +9 -0
  125. package/widget/main.js +202 -0
  126. package/widget/package.json +7 -0
  127. package/widget/preload.js +11 -0
@@ -0,0 +1,273 @@
1
+ // 请求日志视图(R25 批 4):页头(标题行 + 一排筛选)+ 骨架屏 + 空态 + 卡片列表 + 加载更多 + 回到顶部。
2
+ // 契约见 page/shared.js 头部的「R25 组件文件契约」;Vue 的 API 一律写全名,不解构。
3
+ //
4
+ // 根节点是 **fragment**(`<main>` 与 `<button class="back-top">` 两个)—— 改前这两个就是兄弟节点,
5
+ // `.back-top` 靠 `position: fixed` 定位、与 `.main` 无父子选择器关系,所以搬在一起不动布局。
6
+ // fragment 的代价:落在组件标签上的属性与非声明的监听器都会被丢掉,所以 emit 必须逐个声明、逐个接。
7
+ //
8
+ // ⚠ 不变式(改这个文件前先读):
9
+ // 1. `.main` 是**唯一**的滚动容器,且这一份必须是 `v-show` 不能 `v-if`:销毁重建会丢滚动位置和已展开的详情。
10
+ // (会话视图那一份是 v-if —— 它自己两栏内滚,不共享这个滚动位置。)
11
+ // 2. `v-memo` 留在这里的 v-for 上,依赖表 `cardMemo` 是**根传进来的函数**。卡片模板新增读任何响应式状态,
12
+ // 都必须同步给根里的 cardMemo 加一项,漏一个就是「点了没反应」(memo 相等 → 整棵子树跳过重渲)。
13
+ // 3. 滚动相关的三个状态(mainScroll / showTop 与那两个函数)是本批**唯一**搬进组件的状态 ——
14
+ // 它们只服务于这一棵子树:根里的 locateEntry 与 collapseCard 都用 `document.getElementById('card-'+id)`
15
+ // / `ev.target.closest('.card')` 拿元素,不经过这个 ref,所以不需要 defineExpose 递出去。
16
+ // 其余状态(筛选、条目、详情缓存、折叠开关)一个都不搬,理由同批 3:见 agent-log-requirements.md R25。
17
+ window.AACTA = window.AACTA || {};
18
+ AACTA.LogView = {
19
+ name: 'aa-log-view',
20
+ template: `
21
+ <main class="main" ref="mainScroll" v-show="active" @scroll.passive="onMainScroll">
22
+ <div class="page-head">
23
+ <div>
24
+ <h1>请求日志</h1>
25
+ <!-- R33:搜索态下「共 N 条」会撒谎 —— 那个 N 是**当前窗口**的条数(几百到 5000),
26
+ 而搜索覆盖的是全库。两句话不能同时出现,否则「共 2000 条 · 命中 30 条」看着像自相矛盾。 -->
27
+ <div v-if="searchOn" class="statline">全库搜索 · 命中 {{ filtered.length }} 条</div>
28
+ <div v-else class="statline">共 {{ all.length }} 条 · 匹配 {{ filtered.length }} 条<span v-if="capped" style="color:#c26a1b"> · 仅保留最近 5000 条</span><!-- R5:失败聚合入口,点击直接筛出失败条目 --><span v-if="failCount" class="fail-link" @click="$emit('fail-toggle')"> · <b>{{ fStatus==='error' ? '退出失败筛选' : '失败 ' + failCount + ' 条' }}</b></span></div>
29
+ </div>
30
+ <div class="filters">
31
+ <!-- 搜索有**两档**,必须都在提示里说清楚,否则用户拿标题行的总条数对照「匹配 0 条」会以为搜索坏了:
32
+ ① 敲字 = 客户端过滤,只作用于已拉回来的窗口条目(见根里的 filtered 注释);
33
+ ② 回车 = 走后端全文索引(R33 / I10),跨 agent、跨未加载历史,还能搜到 AI 回复与工具正文。
34
+ 不额外加「全库」按钮:.filters 里的 el-input 固定 220px,塞 append 按钮会把输入区挤窄一圈;
35
+ 回车是标准动作,提示里写明白就够,搜完还有横幅可以一键清除。 -->
36
+ <el-input :model-value="fSearch" @update:model-value="v => $emit('update:f-search', v)"
37
+ @keyup.enter="$emit('search-run')"
38
+ :placeholder="searchOn ? '已全库搜索;改词后再回车重搜' : '当前 ' + all.length + ' 条内过滤,回车搜全库'"
39
+ clearable :prefix-icon="searchIcon"></el-input>
40
+ <!-- R10 agent 多选:只在侧栏处于「全部」(fAgent 为空)时显示且有效 ——
41
+ 点了具体 agent 后隐藏,避免「侧栏单选 + 多选」两个筛选打架。空 = 全部,勾 N 个 = 合集 -->
42
+ <el-select v-show="!fAgent" :model-value="fAgents" @update:model-value="v => $emit('update:f-agents', v)" multiple collapse-tags collapse-tags-tooltip
43
+ placeholder="全部 agent(可多选)" clearable filterable class="multi-sel"
44
+ title="agent 多选:仅侧栏为「全部」时生效;空 = 全部" @change="v => $emit('agents-multi', v)">
45
+ <el-option v-for="n in sideAgents.filter(x => !isIconOnly(x))" :key="n" :label="n" :value="n"></el-option>
46
+ </el-select>
47
+ <!-- R10 项目多选:暂时隐藏(2026-09-17 用户拍板:项目筛选整体不用)。逻辑与深链保留,恢复只需解开这段注释
48
+ <el-select v-model="fProjects" multiple collapse-tags collapse-tags-tooltip
49
+ placeholder="全部项目(可多选)" clearable filterable class="multi-sel"
50
+ title="项目多选:空 = 全部" @change="pickProjectsMulti">
51
+ <el-option v-for="p in projectList" :key="p.key" :value="p.key" :title="p.names.join('\\n')"
52
+ :label="p.names.length > 1 ? p.label + '(' + p.names.length + ' 种写法)' : p.label"></el-option>
53
+ </el-select>
54
+ -->
55
+ <!-- 项目单选下拉暂时隐藏(2026-09-17 用户拍板):多选一个就够,单选场景 = 只勾一个。
56
+ fProject 及其 pickProject / 深链逻辑全部保留未动,恢复只需解开这段注释
57
+ <el-select v-model="fProject" placeholder="全部项目" clearable filterable @change="pickProject">
58
+ <el-option v-for="p in projectList" :key="p.key" :value="p.key" :title="p.names.join('\\n')"
59
+ :label="p.names.length > 1 ? p.label + '(' + p.names.length + ' 种写法)' : p.label"></el-option>
60
+ </el-select>
61
+ -->
62
+ <!-- R22 时间范围:与用量统计同款的**具体日期区间**(YYYY-MM-DD,本地自然日),默认最近 2 天;
63
+ 清空 = 全部时间。仍走服务端收窄(显式 from/to,复用 customBounds,挂过午夜不漂移)。
64
+ 符号快捷档(today/7d/30d)从 UI 撤下;旧深链 \`range=\` 链接在 restoreFromHash 里换算成日期区间 -->
65
+ <el-date-picker :model-value="fDates" @update:model-value="v => $emit('update:f-dates', v)" type="daterange" class="rng-picker" value-format="YYYY-MM-DD"
66
+ range-separator="至" start-placeholder="开始" end-placeholder="结束" clearable
67
+ title="具体日期范围(默认最近 2 天);清空 = 全部时间,与用量统计同一套自然日口径"
68
+ @change="v => $emit('range-change', v)"></el-date-picker>
69
+ <el-select :model-value="fStatus" @update:model-value="v => $emit('update:f-status', v)" placeholder="全部状态" clearable class="slim" @change="$emit('status-change')">
70
+ <el-option label="成功" value="ok"></el-option>
71
+ <el-option label="出错" value="error"></el-option>
72
+ </el-select>
73
+ <!-- R9 只看异常:按异常巡检命中集合过滤主列表(与状态/搜索可叠加),没命中时不点亮 -->
74
+ <el-checkbox :model-value="alertOnly" @update:model-value="v => $emit('update:alert-only', v)"
75
+ class="alert-only-cb" title="只显示异常巡检命中的条目(可在异常巡检弹层配置规则)">
76
+ 只看异常<template v-if="alertCount">({{ alertCount }})</template>
77
+ </el-checkbox>
78
+ <el-select :model-value="fLimit" @update:model-value="v => $emit('update:f-limit', v)" class="slim" @change="v => $emit('limit-change', v)">
79
+ <el-option label="50 条/页" :value="50"></el-option>
80
+ <el-option label="100 条/页" :value="100"></el-option>
81
+ <el-option label="200 条/页" :value="200"></el-option>
82
+ <el-option label="500 条/页" :value="500"></el-option>
83
+ </el-select>
84
+ <el-button circle :icon="trendIcon" title="用量统计(按天聚合,跟随当前筛选)" @click="$emit('open-stats', $event)"></el-button>
85
+ <!-- R11 导出增强:原 JSON 按钮升级为「导出当前筛选结果」下拉,选格式(JSON/CSV/Excel)。
86
+ 筛选条件会随文件带出(CSV/Excel 首行、JSON meta),不丢「按什么条件导出」的上下文 -->
87
+ <el-dropdown trigger="click" @command="k => $emit('export-json', k)">
88
+ <el-button circle :icon="downloadIcon" title="导出当前筛选结果"></el-button>
89
+ <template #dropdown>
90
+ <el-dropdown-menu>
91
+ <el-dropdown-item command="json">导出为 JSON</el-dropdown-item>
92
+ <el-dropdown-item command="csv">导出为 CSV(可直接在表格软件打开)</el-dropdown-item>
93
+ <el-dropdown-item command="xls">导出为 Excel</el-dropdown-item>
94
+ </el-dropdown-menu>
95
+ </template>
96
+ </el-dropdown>
97
+ <el-button circle :icon="refreshIcon" title="刷新" @click="$emit('refresh', $event)"></el-button>
98
+ </div>
99
+ </div>
100
+
101
+ <!-- R33 全文搜索的结果条(只在搜索态出现)。三件事必须写全,少一件就会误导:
102
+ ① 搜的是什么词;② 命中多少;③ **索引覆盖到哪了** —— 索引是后台一点点建的,
103
+ 没建完时「没搜到」的意思是「还没索引到」,不写出来就会被读成「没有这条日志」。
104
+ 另有三条如实交代:读不出源文件的条数、命中里有多少条已滑出内存窗口、以及这一轮被服务端
105
+ 单轮条数上限截断了吗(capped —— 少了这条,「命中 200 条」会被读成全库就 200 条)。 -->
106
+ <div v-if="searchOn" class="search-bar">
107
+ <span>全文搜索「<b>{{ searchQ }}</b>」<template v-if="searchRegex">(正则)</template> · 命中 <b>{{ filtered.length }}</b> 条</span>
108
+ <span v-if="searchError" class="sb-err">· {{ searchError }}</span>
109
+ <template v-else>
110
+ <span v-if="searchCapped" class="sb-meta"
111
+ :title="'服务端这一轮只取回前 ' + searchHitsCount + ' 条(/api/search 的 limit,默认 200),全库命中其实更多 —— 上面那个「命中 N 条」说的是取回来的这些。要看得更全:换更具体的词、勾「限定当前时间范围」,或先用侧栏把 agent / 项目收窄'">· 命中不止这些,本次只取回 {{ searchHitsCount }} 条</span>
112
+ <span class="sb-meta">· {{ searchMeta.building ? '索引构建中,已覆盖' : '已索引' }}
113
+ {{ searchMeta.entries || 0 }}/{{ searchMeta.total || 0 }} 条</span>
114
+ <span v-if="searchMeta.stale" class="sb-meta"
115
+ title="这些条目命中了,但已滑出服务端的内存窗口(LRU),这一轮拿不到它们的统计字段">· 另有 {{ searchMeta.stale }} 条在内存窗口外</span>
116
+ <span v-if="searchMeta.unreadable" class="sb-meta"
117
+ title="这几条没有可索引的正文:源文件读不出来了(多半被产品删了),或者这一轮本来就没有正文(例如 atomcode 的空转录——它的对话在 .snapshot 里,不在 jsonl 里)。它们仍可按会话名 / 模型 / 工具名搜到">· {{ searchMeta.unreadable }} 条没有正文</span>
118
+ <span v-if="!searchNarrow" class="sb-meta">· 未套用时间范围</span>
119
+ </template>
120
+ <span class="sb-spacer"></span>
121
+ <el-checkbox :model-value="searchNarrow" @update:model-value="v => $emit('search-toggle-narrow', v)"
122
+ title="默认搜全部历史(这正是全文搜索的意义);勾上 = 只搜当前日期区间里的条目">限定当前时间范围</el-checkbox>
123
+ <el-button size="small" @click="$emit('search-clear')">清除</el-button>
124
+ </div>
125
+
126
+ <!-- 首份快照还没回来 → 骨架屏。形状照抄真卡片(顶行 + 三格指标 + 脚注),一眼看出是「在加载」而不是「没有」 -->
127
+ <div v-if="booting" class="sk-list">
128
+ <div v-for="n in 4" :key="n" class="sk-card">
129
+ <div class="sk-top">
130
+ <div class="sk-av"></div>
131
+ <div class="sk-body">
132
+ <div class="sk-line l1"></div>
133
+ <div class="sk-line l2"></div>
134
+ </div>
135
+ </div>
136
+ <div class="sk-metrics">
137
+ <div class="sk-box"></div>
138
+ <div class="sk-box"></div>
139
+ <div class="sk-box"></div>
140
+ </div>
141
+ <div class="sk-foot"><div class="sk-line l3"></div></div>
142
+ </div>
143
+ </div>
144
+ <div v-else-if="!visible.length" class="empty">
145
+ <!-- 扫描中优先:冷启动时列表本来就是空的,写「暂无匹配」会被读成「没数据/坏了」 -->
146
+ <template v-if="scanning">正在扫描…({{ scanning.done }}/{{ scanning.total }})</template>
147
+ <!-- 搜索无果且范围里有未进窗口的历史条目时,必须说明「为什么搜不到」,否则用户以为没有这条日志 -->
148
+ <template v-else-if="fSearch && !filtered.length && rangeTotal > all.length">当前 {{ all.length }} 条中无匹配;该范围内共 {{ rangeTotal }} 条,历史条目未参与搜索,请调大「条/页」后重试</template>
149
+ <template v-else>暂无匹配的请求记录</template>
150
+ </div>
151
+
152
+ <!-- v-memo:展开/收起一张卡时 expandedId 一变,整棵 vnode 子树都会重建 —— 没有 memo 的话
153
+ 200 张卡(含 ~2000 个 el-tooltip/el-icon 实例)全部重新 diff,实测主线程 65~150ms。
154
+ memo 依赖见**根里的** cardMemo(它读的是根那一份状态,作函数 props 递进来):
155
+ 收起的卡只看条目本体和 live 标志,展开的卡把详情相关状态全列上。 -->
156
+ <aa-log-card v-for="e in visible" :key="e.id" v-memo="cardMemo(e)"
157
+ :e="e" :expanded="expandedId === e.id" :detail="detailOf(e.id)" :secs="secs"
158
+ :call-open="callOpen" :tool-json-fold="toolJsonFold" :full-ids="fullIds"
159
+ :title-of="titleOf" :is-live="isLive"
160
+ :compare-sel="compareIsOf(e.id)"
161
+ @toggle="onToggle" @open-session="onOpenSession" @collapse="onCollapse" @load-full="onLoadFull"
162
+ @fold-json="onFoldJson" @fold-callout="onFoldCallout" @toggle-sec="onToggleSec" @reload-detail="onReloadDetail" @export-detail="onExportDetail" @export-repro="onExportRepro" @compare="onCompare"></aa-log-card>
163
+
164
+ <el-button v-if="shown < filtered.length" id="loadmore" plain @click="$emit('load-more')">
165
+ 加载更多(还有 {{ filtered.length - shown }} 条)
166
+ </el-button>
167
+ </main>
168
+
169
+ <!-- R21 回到顶部悬浮按钮:fixed 右下角,滚动超过一屏才出现(见本文件的 onMainScroll);
170
+ 展开详情时上移让开吸底的「收起」(.detail-foot,sticky bottom:0),窄窗口也不遮挡 -->
171
+ <button v-show="showTop && active" class="back-top" :class="{shift: expandedId !== null}" title="回到顶部" @click="scrollMainTop">
172
+ <el-icon><Top></Top></el-icon>
173
+ </button>`,
174
+ props: {
175
+ // view === 'log':两个视图共用一个根,靠这个开关显隐(v-show,见头部不变式 1)
176
+ active: Boolean,
177
+ booting: Boolean,
178
+ scanning: { type: Object, default: null },
179
+ // 条目:all 是窗口本体(标题行/搜索提示要它的长度),filtered 是客户端筛选结果,visible 是本页要画的
180
+ all: { type: Array, default: () => [] },
181
+ filtered: { type: Array, default: () => [] },
182
+ visible: { type: Array, default: () => [] },
183
+ capped: Boolean,
184
+ failCount: { type: Number, default: 0 },
185
+ rangeTotal: { type: Number, default: 0 },
186
+ // 五个筛选(状态全在根:深链 restoreFromHash / snapUrl / 侧栏共用同一份)
187
+ fSearch: { type: String, default: '' },
188
+ fAgents: { type: Array, default: () => [] },
189
+ fAgent: { type: String, default: '' },
190
+ fDates: { type: Object, default: null },
191
+ fStatus: { type: String, default: '' },
192
+ fLimit: { type: Number, default: 200 },
193
+ // R9 只看异常:只显示命中集合里的条目(alertOnly 归根,命中计数进标题提示)
194
+ alertOnly: { type: Boolean, default: false },
195
+ alertCount: { type: Number, default: 0 },
196
+ // 项目那两个筛选 2026-09-17 起停用,模板里只剩注释块(page-check 连注释一起扫,故要声明);
197
+ // 照样从根递值进来,将来解开注释即刻可用。
198
+ fProjects: { type: Array, default: () => [] },
199
+ fProject: { type: String, default: '' },
200
+ projectList: { type: Array, default: () => [] },
201
+ pickProject: { type: Function, required: true },
202
+ pickProjectsMulti: { type: Function, required: true },
203
+ sideAgents: { type: Array, default: () => [] },
204
+ isIconOnly: { type: Function, required: true },
205
+ shown: { type: Number, default: 200 },
206
+ // R33 全文搜索(I10):搜索态下 filtered 的数据源在根里换成了服务端命中集,本组件只负责显示与转发。
207
+ // searchMeta = 服务端索引的覆盖情况(entries/total/building)叠加本次查询的 stale/unreadable。
208
+ searchOn: { type: Boolean, default: false },
209
+ searchQ: { type: String, default: '' },
210
+ searchRegex: { type: Boolean, default: false },
211
+ searchBusy: { type: Boolean, default: false },
212
+ searchNarrow: { type: Boolean, default: false },
213
+ searchError: { type: String, default: '' },
214
+ searchMeta: { type: Object, default: () => ({}) },
215
+ // capped 由服务端算(命中数 > 这一轮的 limit),页面只负责把它说出来;hits 数是取回来的条数,
216
+ // 不是全库命中数 —— 横幅上那两个数各说各的,正是这条提示要防的误读。
217
+ searchCapped: { type: Boolean, default: false },
218
+ searchHitsCount: { type: Number, default: 0 },
219
+ // 卡片与详情:expandedId 决定哪张展开(也决定「收起」按钮让位),secs 是当前展开卡那三个长列表视图
220
+ expandedId: { type: [String, Number], default: null },
221
+ secs: { type: Object, default: null },
222
+ callOpen: { type: Object, default: () => new Set() },
223
+ toolJsonFold: { type: Object, default: () => new Set() },
224
+ fullIds: { type: Object, default: () => new Set() },
225
+ // I9 两轮 diff:判这条在不在对比组里(函数 prop —— 它读根里的对比 Set,不搬进组件)
226
+ compareIsOf: { type: Function, required: true },
227
+ // 四个读根状态的函数以函数形态作 props 传入(批 2/3 的先例):memo 表也在根
228
+ cardMemo: { type: Function, required: true },
229
+ detailOf: { type: Function, required: true },
230
+ titleOf: { type: Function, required: true },
231
+ isLive: { type: Function, required: true },
232
+ // Element Plus 图标是**表达式**不是组件标签,全局注册帮不上,只能从根递进来(批 3 的 searchIcon 同款)
233
+ searchIcon: { type: [Object, Function], required: true },
234
+ trendIcon: { type: [Object, Function], required: true },
235
+ downloadIcon: { type: [Object, Function], required: true },
236
+ refreshIcon: { type: [Object, Function], required: true },
237
+ },
238
+ emits: ['update:f-search', 'update:f-agents', 'agents-multi', 'update:f-dates', 'range-change',
239
+ 'update:f-status', 'status-change', 'update:f-limit', 'limit-change', 'fail-toggle',
240
+ 'update:alert-only',
241
+ 'open-stats', 'export-json', 'refresh', 'load-more',
242
+ 'toggle', 'open-session', 'collapse', 'load-full', 'fold-json', 'fold-callout', 'toggle-sec', 'reload-detail', 'export-detail', 'export-repro',
243
+ // I9 两轮 diff:卡片「对比」点上来(选满两条由根弹开)
244
+ 'compare',
245
+ // R33:搜索框回车走服务端全文搜索;清除与「限定时间范围」也在横幅上转发回根
246
+ 'search-run', 'search-clear', 'search-toggle-narrow'],
247
+ setup(props, { emit }) {
248
+ // 滚动三件套(本批唯一搬进来的状态,见头部不变式 3):.main 是唯一滚动容器,
249
+ // 只在滚动超过一屏时出现按钮;点击平滑回顶。展开详情时按钮上移由模板的 :class.shift 负责。
250
+ const mainScroll = Vue.ref(null);
251
+ const showTop = Vue.ref(false);
252
+ function onMainScroll() {
253
+ const el = mainScroll.value;
254
+ showTop.value = !!(el && el.scrollTop > el.clientHeight);
255
+ }
256
+ function scrollMainTop() {
257
+ const el = mainScroll.value;
258
+ if (el) el.scrollTo({ top: 0, behavior: 'smooth' });
259
+ }
260
+ // 卡片转发上来的八个事件用**成员表达式**监听(不是内联语句):Vue 直接把函数交给监听器,
261
+ // 于是 emit 的全部实参都原样传到根 —— 内联的 $event 只有第一个,collapse 的 $event、
262
+ // fold-json 的 (id, i, which) 会静默丢掉(静态检查查不出,实测才发现,见批 3 的先例 ④)。
263
+ const fwd = k => (...a) => emit(k, ...a);
264
+ return {
265
+ mainScroll, showTop, onMainScroll, scrollMainTop,
266
+ onToggle: fwd('toggle'), onOpenSession: fwd('open-session'), onCollapse: fwd('collapse'),
267
+ onLoadFull: fwd('load-full'), onFoldJson: fwd('fold-json'), onFoldCallout: fwd('fold-callout'),
268
+ onToggleSec: fwd('toggle-sec'), onReloadDetail: fwd('reload-detail'), onExportDetail: fwd('export-detail'),
269
+ onExportRepro: fwd('export-repro'),
270
+ onCompare: fwd('compare'),
271
+ };
272
+ }
273
+ };