@aiyiran/myclaw 1.1.168 → 1.1.169

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.
@@ -327,22 +327,32 @@
327
327
 
328
328
  function buildTestDonePrompt() {
329
329
  var rec = window.myclawLastDebugRecord;
330
- var lines = [
331
- "我刚才已经完成了测试。",
332
- "请你检查我的测试数据 debug/debug-record.html,重点查看里面的 JS 日志。",
333
- ];
334
-
335
- if (rec && rec.debugRecordPath) {
336
- lines.push("如果你能读取本地路径,也可以查看:" + rec.debugRecordPath);
330
+ var lines = ["我刚才已经完成了测试。"];
331
+
332
+ // 调试数据落在 debug-data.js(window.__DEBUG_DATA__ = {...}),AI 直接读这个
333
+ // 数据文件即可,不要读同目录的 debug-record.html(那只是给人看的渲染壳)。
334
+ var dataFile = null;
335
+ if (rec) {
336
+ if (rec.debugRecordPath) {
337
+ dataFile = rec.debugRecordPath.replace(/debug-record\.html$/, "debug-data.js");
338
+ } else if (rec.workspace) {
339
+ dataFile = rec.workspace + "/debug/debug-data.js";
340
+ }
341
+ }
342
+ if (dataFile) {
343
+ lines.push("请你读取调试数据文件:" + dataFile);
344
+ } else {
345
+ lines.push("请你读取本次测试的调试数据文件 debug/debug-data.js。");
337
346
  }
347
+ lines.push("它是一行 `window.__DEBUG_DATA__ = {...}` 的赋值,里面的 events 数组就是完整运行现场;不要去读同目录的 debug-record.html。");
338
348
  if (rec && rec.entryFile) {
339
349
  lines.push("我刚才测试的文件是:" + rec.entryFile);
340
350
  }
341
351
 
342
352
  lines.push("");
343
353
  lines.push("请帮我判断:我们之前的问题是否已经修复。");
344
- lines.push("如果已经修复,请简单说明你从日志里看到了什么证据。");
345
- lines.push("如果还没有修复,请指出日志里最关键的问题,并建议下一步最小修改。");
354
+ lines.push("如果已经修复,请简单说明你从 events 里看到了什么证据。");
355
+ lines.push("如果还没有修复,请指出 events 里最关键的问题,并建议下一步最小修改。");
346
356
 
347
357
  return lines.join("\n");
348
358
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiyiran/myclaw",
3
- "version": "1.1.168",
3
+ "version": "1.1.169",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -27,38 +27,39 @@ python3 scripts/extract_chat.py '["url1","url2","url3"]' <output-dir>
27
27
 
28
28
  ## 输出文件
29
29
 
30
+ **只产出两个文件**,不再为每个会话单独落 js(所有会话的完整数据都汇入 `index.js`):
31
+
30
32
  | 文件 | 说明 |
31
33
  |---|---|
32
- | `01-<session-name>.js` | 1 个会话的数据 |
33
- | `02-<session-name>.js` | 2 个会话的数据 |
34
- | `...` | |
35
- | `index.js` | 所有会话的索引列表(仅批量模式生成) |
36
- | `chat_history.js` | 向后兼容,指向第一个会话 |
37
-
38
- 每个 JS 文件结构:
39
-
40
- ```javascript
41
- const chatData = {
42
- "session": "session-name",
43
- "session_id": "agent:xxx:yyy",
44
- "total_pairs": N,
45
- "initiator": "...",
46
- "conversations": [
47
- { "user": "...", "user_time": "...", "ai": "...", "ai_time": "..." },
48
- ...
49
- ]
50
- };
51
- ```
34
+ | `index.js` | 唯一数据产物:`chatIndex`(会话元数据列表)+ `chatAll`(每个会话的完整对话) |
35
+ | `index.html` | 查看器,复制自 `assets/chat-history-template.html`,只加载 `index.js` |
52
36
 
53
37
  `index.js` 结构:
54
38
 
55
39
  ```javascript
40
+ // chatIndex:用于会话切换下拉框的元数据
56
41
  const chatIndex = [
57
- { "index": 1, "session": "...", "js_file": "01-xxx.js", "total_pairs": 10 },
58
- { "index": 2, "session": "...", "js_file": "02-yyy.js", "total_pairs": 8 },
42
+ { "session_id": "agent:xxx:main", "workspace_name": "xxx", "session_name": "main",
43
+ "total_pairs": 10, "first_time": "...", "last_time": "...", "work_url": "" },
44
+ ...
59
45
  ];
46
+
47
+ // chatAll:按 session_id 索引的完整会话数据(HTML 渲染的真正数据源)
48
+ const chatAll = {
49
+ "agent:xxx:main": {
50
+ "workspace_name": "xxx", "session_name": "main", "session_id": "agent:xxx:main",
51
+ "total_pairs": 10, "first_time": "...", "last_time": "...", "work_url": "",
52
+ "conversations": [
53
+ { "user": "...", "user_time": "...", "ai": "...", "ai_time": "..." },
54
+ ...
55
+ ]
56
+ },
57
+ ...
58
+ };
60
59
  ```
61
60
 
61
+ > 单会话和批量模式产物一致,区别只是 `chatIndex` / `chatAll` 里的会话数量。
62
+
62
63
  ## 工作流
63
64
 
64
65
  ### Step 1: 解析输入
@@ -85,14 +86,14 @@ URL 示例:`https://claw1.kekouen.cn/chat?session=agent%3Ac108-v1-1811%3Amain`
85
86
  3. 多条 AI 回复合并为一条
86
87
  4. 跳过 `toolResult` 事件和空文本消息
87
88
 
88
- ### Step 4: 生成 JS 文件
89
+ ### Step 4: 生成 index.js
89
90
 
90
- 批量模式下,每个会话生成独立的 `NN-<name>.js`,并额外生成 `index.js` 索引。
91
+ 所有会话的数据在内存中汇总,一次性写入唯一的 `index.js`(`chatIndex` + `chatAll`),不为单个会话单独落文件。
91
92
 
92
93
  ### Step 5: 渲染
93
94
 
94
- 将模板 `assets/chat-history-template.html` 复制到输出目录。
95
- 模板会加载 `chat_history.js`(单会话)或可通过 `index.js` 切换(多会话)。
95
+ 将模板 `assets/chat-history-template.html` 复制到输出目录为 `index.html`。
96
+ 模板只加载 `index.js`,通过 `chatIndex` 渲染会话切换下拉框、从 `chatAll` 取完整对话。
96
97
 
97
98
  模板展示三种时间指标:
98
99
  - **课程流逝** — 距首条消息的时间
@@ -116,8 +117,8 @@ python3 scripts/extract_chat.py "https://claw1.kekouen.cn/chat?session=agent%3Ac
116
117
  ```
117
118
 
118
119
  输出:
119
- - `01-main.js`
120
- - `chat_history.js`(= 01-main.js 的副本)
120
+ - `index.js`(含 1 个会话)
121
+ - `index.html`
121
122
 
122
123
  ### 批量处理
123
124
 
@@ -131,9 +132,5 @@ python3 scripts/extract_chat.py '[
131
132
  ```
132
133
 
133
134
  输出:
134
- - `01-main.js`
135
- - `02-main.js`
136
- - `03-每天一个小习惯·宠物养成_1813.js`
137
- - `04-我的金币任务板_1810.js`
138
- - `index.js`
139
- - `chat_history.js`(= 01-main.js 的副本)
135
+ - `index.js`(含 4 个会话,全部数据在 `chatAll` 里)
136
+ - `index.html`
@@ -23,10 +23,11 @@ Usage:
23
23
  python3 extract_chat.py --scan [--html]
24
24
 
25
25
  输出(默认到当前 workspace /root/.openclaw/workspace-teacher):
26
- - 每个会话生成独立的 <agentId>_<sessionName>_<date>_<time>.js
27
- - 生成 index.js,包含所有会话的元信息列表(含 work_url 字段)
28
- - 生成 chat_history.js(向后兼容,指向第一个会话)
29
- - --html 时额外生成 summary.html(含可点击链接)
26
+ - index.js:唯一数据产物,含 chatIndex(会话元信息列表,含 work_url 字段)
27
+ + chatAll(每个会话的完整对话,按 session_id 索引)
28
+ - index.html:查看器,复制自 assets/chat-history-template.html,只加载 index.js
29
+ - 不再为每个会话单独落 js 文件
30
+ - --scan 模式额外生成 作品扫描_<时间>.html(含可点击链接)
30
31
 
31
32
  Host 检测:
32
33
  - 自动从 /root/.openclaw/myclaw/server/config.json 读取
@@ -34,7 +35,7 @@ Host 检测:
34
35
 
35
36
  work_url 字段:
36
37
  - 默认为空字符串
37
- - AI 可后续手动编辑 JS 文件填入学生作品链接
38
+ - AI 可后续编辑 index.js 中对应会话的 work_url 填入学生作品链接
38
39
  """
39
40
 
40
41
  import json
@@ -166,7 +167,8 @@ def count_conversations(jsonl_path):
166
167
  messages.append({
167
168
  'role': role,
168
169
  'text': text.strip(),
169
- 'has_text': bool(text.strip())
170
+ 'has_text': bool(text.strip()),
171
+ 'timestamp': timestamp
170
172
  })
171
173
 
172
174
  # Count conversation pairs (user messages with at least one AI reply)
@@ -183,9 +185,13 @@ def count_conversations(jsonl_path):
183
185
  ai_messages.append(messages[j])
184
186
  j += 1
185
187
  ai_text = '\n\n'.join([m['text'] for m in ai_messages if m['has_text']])
188
+ user_time = msg.get('timestamp', '')
189
+ ai_time = ai_messages[0].get('timestamp', '') if ai_messages else ''
186
190
  conversations.append({
187
191
  'user': user_text,
188
192
  'ai': ai_text,
193
+ 'user_time': user_time,
194
+ 'ai_time': ai_time,
189
195
  })
190
196
  i += 1
191
197
  else:
@@ -210,17 +216,26 @@ def build_filename(session_key):
210
216
  parts = session_key.split(':')
211
217
  agent_id = parts[1] if len(parts) > 1 else 'unknown'
212
218
  session_name = parts[2] if len(parts) > 2 else 'main'
219
+ # Use last part (UUID or 'main') as unique suffix to avoid collisions
220
+ unique_id = parts[-1] if len(parts) > 3 else session_name
213
221
  agent_safe = sanitize_filename(agent_id)
214
222
  name_safe = sanitize_filename(session_name)
215
- return f"{agent_safe}_{name_safe}.js"
223
+ unique_safe = sanitize_filename(unique_id)
224
+ if unique_safe == name_safe:
225
+ return f"{agent_safe}_{name_safe}.js"
226
+ return f"{agent_safe}_{name_safe}_{unique_safe}.js"
216
227
 
217
228
 
218
- def generate_js(conversations, session_key, first_ts, last_ts, output_path):
229
+ def build_chat_data(conversations, session_key, first_ts, last_ts):
230
+ """构建单个会话的完整数据对象(仅内存,不写盘)。
231
+
232
+ 数据最终全部汇入 index.js 的 chatAll,不再为每个会话单独落 js 文件。
233
+ """
219
234
  parts = session_key.split(':')
220
235
  workspace_name = parts[1] if len(parts) > 1 else 'unknown'
221
236
  session_name = parts[2] if len(parts) > 2 else 'main'
222
237
 
223
- output_data = {
238
+ return {
224
239
  'workspace_name': workspace_name,
225
240
  'session_name': session_name,
226
241
  'session_id': session_key,
@@ -230,15 +245,6 @@ def generate_js(conversations, session_key, first_ts, last_ts, output_path):
230
245
  'work_url': '',
231
246
  'conversations': conversations
232
247
  }
233
- js_content = f'''// Chat History - {session_name}
234
- // Session Key: {session_key}
235
- // Generated by chat-history-extractor skill
236
-
237
- const chatData = {json.dumps(output_data, ensure_ascii=False, indent=2)};
238
- '''
239
- with open(output_path, 'w', encoding='utf-8') as f:
240
- f.write(js_content)
241
- return len(conversations), session_name
242
248
 
243
249
 
244
250
  def session_key_to_url(session_key, host=None):
@@ -387,57 +393,43 @@ def process_one(url_or_key, output_dir, index):
387
393
  conversations, first_ts, last_ts = parse_jsonl_to_conversations(jsonl_path)
388
394
  print(f"[{index}] {len(conversations)} pairs")
389
395
 
390
- js_filename = build_filename(session_key)
391
- js_path = os.path.join(output_dir, js_filename)
392
- count, _ = generate_js(conversations, session_key, first_ts, last_ts, js_path)
393
- print(f"[{index}] Generated: {js_filename}")
396
+ data = build_chat_data(conversations, session_key, first_ts, last_ts)
394
397
 
395
398
  return {
396
399
  'index': index,
397
400
  'session_key': session_key,
398
401
  'session_name': session_name,
399
402
  'agent_id': agent_id,
400
- 'js_file': js_filename,
401
- 'total_pairs': count,
402
- 'first_time': parse_time(first_ts) if first_ts else '',
403
- 'last_time': parse_time(last_ts) if last_ts else '',
403
+ 'total_pairs': data['total_pairs'],
404
+ 'first_time': data['first_time'],
405
+ 'last_time': data['last_time'],
404
406
  'work_url': '',
407
+ 'data': data, # 完整会话数据,汇入 index.js 的 chatAll
405
408
  }
406
409
 
407
410
 
408
- def generate_index_js(output_dir):
409
- """Scan output_dir for all .js files, merge into index.js with full data."""
411
+ def generate_index_js(results, output_dir):
412
+ """直接用内存中的 results 拼出唯一产物 index.js(chatIndex + chatAll)。
413
+
414
+ 不再为每个会话单独落 js 文件——每个会话的完整对话都进 chatAll,
415
+ HTML 只加载 index.js 即可渲染全部会话。
416
+ """
410
417
  all_entries = []
411
418
  all_data = {}
412
419
 
413
- for fname in sorted(os.listdir(output_dir)):
414
- if not fname.endswith('.js') or fname in ('index.js', 'chat_history.js'):
415
- continue
416
- fpath = os.path.join(output_dir, fname)
417
- mtime = int(os.path.getmtime(fpath))
418
- try:
419
- with open(fpath, 'r', encoding='utf-8') as f:
420
- fcontent = f.read()
421
- m = re.search(r'const chatData = (\{.*\});?\s*$', fcontent, re.DOTALL)
422
- if not m:
423
- continue
424
- data = json.loads(m.group(1))
425
- sid = data.get('session_id', fname.replace('.js', ''))
426
- all_data[sid] = data
427
- all_entries.append({
428
- 'workspace_name': data.get('workspace_name', ''),
429
- 'session_name': data.get('session_name', ''),
430
- 'session_id': sid,
431
- 'js_file': fname,
432
- 'total_pairs': data.get('total_pairs', 0),
433
- 'first_time': data.get('first_time', ''),
434
- 'last_time': data.get('last_time', ''),
435
- 'work_url': data.get('work_url', ''),
436
- 'mtime': mtime,
437
- })
438
- except Exception as ex:
439
- print(' [WARN] Skip %s: %s' % (fname, ex))
440
- continue
420
+ for r in results:
421
+ data = r['data']
422
+ sid = data['session_id']
423
+ all_data[sid] = data
424
+ all_entries.append({
425
+ 'workspace_name': data.get('workspace_name', ''),
426
+ 'session_name': data.get('session_name', ''),
427
+ 'session_id': sid,
428
+ 'total_pairs': data.get('total_pairs', 0),
429
+ 'first_time': data.get('first_time', ''),
430
+ 'last_time': data.get('last_time', ''),
431
+ 'work_url': data.get('work_url', ''),
432
+ })
441
433
 
442
434
  now_ts = int(time.time())
443
435
  idx = '// Chat History Index - %d session(s)\n' % len(all_entries)
@@ -546,7 +538,7 @@ def main():
546
538
 
547
539
  os.makedirs(output_dir, exist_ok=True)
548
540
 
549
- # Clean old JS + index.html before fresh extraction
541
+ # 清理旧产物(含历史遗留的单会话 js)后再重新生成
550
542
  for old_f in os.listdir(output_dir):
551
543
  if old_f.endswith('.js') or old_f == 'index.html':
552
544
  try:
@@ -564,8 +556,8 @@ def main():
564
556
  print("\nERROR: No sessions processed successfully.")
565
557
  sys.exit(1)
566
558
 
567
- # Always regenerate index.js by scanning directory
568
- generate_index_js(output_dir)
559
+ # 唯一数据产物:index.js(chatIndex + chatAll)
560
+ generate_index_js(results, output_dir)
569
561
 
570
562
  # Copy chat-history-template.html as index.html
571
563
  import shutil