asmgr 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -55,11 +55,11 @@ asmgr show --file /path/to/session.jsonl.zstd --format dialogue
55
55
  asmgr html <session-id> --agent dsh -o dsh-session.html
56
56
  ```
57
57
 
58
- 以官方 [`dsh-v0.1.5-rc.1`](https://github.com/deepseek-ai/deepseek-harness/tree/dsh-v0.1.5-rc.1) 为基线,直接使用 `dsh-session-format-catalog` 的 v0→v3 解码/迁移及 `dsh-session/surface` 的原始追加消息筛选。迁移在内存中进行,不依赖本机 DSH 安装、不启动插件、不改写源日志。主干包含直接用户输入、助手正文,以及原生或 PTC 调用中 `ask_user_question` 的题目、全部选项与匹配的回答;普通工具的参数和结果完全不显示。回答按官方格式记录,不额外推断回答者身份。
58
+ 以官方 [`dsh-v0.1.5-rc.1`](https://github.com/deepseek-ai/deepseek-harness/tree/dsh-v0.1.5-rc.1) 为基线,直接使用 `dsh-session-format-catalog` 的 v0→v3 解码/迁移及 `dsh-session/surface` 的原始追加消息筛选。v4 存档复用官方 `dsh-session-format-v3-to-v4` 所依赖的 released v2 物理帧解码,再按 v4 词汇表(一等 tool-role 结果、`compact-checkpoint` 等生产者自有 source kind)投影;不升级已锁定的官方依赖。迁移在内存中进行,不依赖本机 DSH 安装、不启动插件、不改写源日志。主干包含直接用户输入、助手正文,以及原生或 PTC 调用中 `ask_user_question` 的题目、全部选项与匹配的回答;普通工具的参数和结果完全不显示。回答按官方格式记录,不额外推断回答者身份。
59
59
 
60
60
  范围保持有限:不解释未知插件事件、注入上下文、失败模型尝试或任意 `meta`;不把压缩 replacement 当成新对话,不重复拼接 fork 的父会话。只显示官方 compact checkpoint 对应的摘要。图片/文件只显示占位符并提示损失;完整工具仍在 text/HTML/Markdown 中保留。HTML/Markdown 暂无独立的主干导出开关。
61
61
 
62
- 压缩日志需要运行时提供 Zstandard API(Node.js ≥ 22.15);运行时缺少帧解码能力时明确报错,不把未读取内容当空会话。未知版本、官方迁移器拒绝的旧日志、损坏或未写完的文件会报出路径与原因,不尝试自定义补救,也不会自动回退到旧一代文件。已知官方边界包括 v0 中的 `subagent/descriptor.version: 2`:最新迁移器明确拒绝它;不能仅把该字段改成 3 来冒充兼容。
62
+ 压缩日志需要运行时提供 Zstandard API(Node.js ≥ 22.15);运行时缺少帧解码能力时明确报错,不把未读取内容当空会话。未知版本(含比已支持最高代 v4 更新的存档)、官方迁移器拒绝的旧日志、损坏或未写完的文件会报出路径与原因,不尝试自定义补救,也不会自动回退到旧一代文件。已知官方边界包括 v0 中的 `subagent/descriptor.version: 2`:最新迁移器明确拒绝它;不能仅把该字段改成 3 来冒充兼容。
63
63
 
64
64
  ## <a id="install"></a>安装
65
65
 
@@ -179,7 +179,9 @@ asmgr search "database migration" --session <session-id> # 只在一个会话
179
179
 
180
180
  每条命中是一行 tab 分隔、以 `project` 列(cwd 最近的含 `.git` 祖先目录;找不到 `.git` 祖先时为 cwd 原值,无 cwd 时为 `(unscoped)`)开头:`project`、`agent`、`session-id`、`#条目`、`role/kind`、`摘录`。
181
181
 
182
- `--session <id>` 把搜索限定到一个会话(先精确匹配 id,否则按前缀匹配)——用来在**当前这个会话**里按关键词找模型回复,不必先用 `--file` 指路径。
182
+ `--session <id>` 把搜索限定到一个会话(先精确匹配 id,否则按前缀匹配;DSH 的 id 是 `session-<uuid>`,直接给裸 uuid 也可以)——用来在**当前这个会话**里按关键词找模型回复,不必先用 `--file` 指路径。
183
+
184
+ 跨会话搜索对每个会话独立解析:遇到官方迁移器拒绝或损坏的存档时,在 stderr 逐条报出路径与原因后跳过,不中断其余会话的检索;stdout 的命中行不受影响。
183
185
 
184
186
  ### `asmgr import`
185
187
 
package/dist/asmgr.mjs CHANGED
@@ -43220,7 +43220,7 @@ var {
43220
43220
  var BIN_NAME = "asmgr";
43221
43221
 
43222
43222
  // src/cli/version.ts
43223
- var VERSION = true ? "0.3.0" : devVersion();
43223
+ var VERSION = true ? "0.4.1" : devVersion();
43224
43224
 
43225
43225
  // src/core/index.ts
43226
43226
  import { stat as stat2 } from "node:fs/promises";
@@ -53417,6 +53417,7 @@ function deriveEventMessage2(event) {
53417
53417
  }
53418
53418
 
53419
53419
  // src/core/adapters/dsh.ts
53420
+ var DSH_NATIVE_VERSION = 4;
53420
53421
  function isDshHeader(value) {
53421
53422
  const row2 = record3(value);
53422
53423
  return row2.type === "session" && typeof row2.version === "number" && typeof row2.id === "string";
@@ -53442,19 +53443,26 @@ function selectDshGenerations(paths) {
53442
53443
  }
53443
53444
  return [...other, ...selected.values()].sort();
53444
53445
  }
53446
+ function readV4Header(header) {
53447
+ return { ...releasedV2SessionFormatCodec.decodeHeader({ ...record3(header), version: 2 }), version: DSH_NATIVE_VERSION };
53448
+ }
53445
53449
  function headerRef(path3, header) {
53446
- const result = sessionFormatCatalog.readHeader(header);
53447
- if (result.status === "unsupported" || result.status === "malformed") throw new Error(result.reason);
53450
+ const stored = record3(header).version;
53451
+ if (typeof stored === "number" && stored > DSH_NATIVE_VERSION) {
53452
+ throw new Error(`DSH \u4F1A\u8BDD\u4F7F\u7528\u66F4\u65B0\u7684\u5B58\u6863\u683C\u5F0F v${stored}\uFF0C\u672C\u7248\u672C\u6700\u9AD8\u652F\u6301 v${DSH_NATIVE_VERSION}`);
53453
+ }
53454
+ const decoded = stored === DSH_NATIVE_VERSION ? { storedVersion: DSH_NATIVE_VERSION, header: readV4Header(header) } : sessionFormatCatalog.readHeader(header);
53455
+ if ("reason" in decoded) throw new Error(decoded.reason);
53448
53456
  const namedVersion = dshLogVersion(path3);
53449
- if (namedVersion !== void 0 && namedVersion !== result.storedVersion) {
53450
- throw new Error(`DSH \u6587\u4EF6\u540D\u7248\u672C v${namedVersion} \u4E0E\u6587\u4EF6\u5934 v${result.storedVersion} \u4E0D\u7B26`);
53457
+ if (namedVersion !== void 0 && namedVersion !== decoded.storedVersion) {
53458
+ throw new Error(`DSH \u6587\u4EF6\u540D\u7248\u672C v${namedVersion} \u4E0E\u6587\u4EF6\u5934 v${decoded.storedVersion} \u4E0D\u7B26`);
53451
53459
  }
53452
53460
  return {
53453
53461
  agent: "dsh",
53454
- id: result.header.id,
53462
+ id: decoded.header.id,
53455
53463
  path: path3,
53456
- cwd: result.header.cwd,
53457
- startedAt: new Date(result.header.createdAt).toISOString(),
53464
+ cwd: decoded.header.cwd,
53465
+ startedAt: new Date(decoded.header.createdAt).toISOString(),
53458
53466
  source: { kind: "events", path: path3, lossy: false }
53459
53467
  };
53460
53468
  }
@@ -53472,6 +53480,19 @@ async function discoverDsh(root5) {
53472
53480
  for (const path3 of files) refs.push(await refFromDshFile(path3));
53473
53481
  return refs;
53474
53482
  }
53483
+ function createDshRestore(headerRow) {
53484
+ if (record3(headerRow).version !== DSH_NATIVE_VERSION) {
53485
+ return sessionFormatCatalog.createRestore(headerRow, { recovery: "strict", validation: "current" });
53486
+ }
53487
+ const decoder = releasedV2SessionFormatCodec.createDecoder({ ...record3(headerRow), version: 2 }, "strict");
53488
+ const header = { ...decoder.header, version: DSH_NATIVE_VERSION };
53489
+ const collector = new SessionFormatEventCollector();
53490
+ return {
53491
+ header,
53492
+ decodeRow: (row2) => decoder.decodeRow(row2, collector),
53493
+ finish: () => ({ header, inheritedEventCount: decoder.finish(collector), events: collector.values })
53494
+ };
53495
+ }
53475
53496
  async function parseDsh(ref) {
53476
53497
  try {
53477
53498
  let restore;
@@ -53479,7 +53500,7 @@ async function parseDsh(ref) {
53479
53500
  for await (const row2 of iterateJsonl(ref.path)) {
53480
53501
  if (!restore) {
53481
53502
  source = headerRef(ref.path, row2);
53482
- restore = sessionFormatCatalog.createRestore(row2, { recovery: "strict", validation: "current" });
53503
+ restore = createDshRestore(row2);
53483
53504
  } else restore.decodeRow(row2);
53484
53505
  }
53485
53506
  if (!restore) throw new Error("\u7A7A\u4F1A\u8BDD\u6587\u4EF6");
@@ -53595,7 +53616,8 @@ ${answer}`,
53595
53616
  if (typeof data.compactionId === "string") summaries.set(data.compactionId, visibleText(data.summary));
53596
53617
  } else if (raw2.type === "user/message" && isReplacementSurfaceEvent(event)) {
53597
53618
  const source = record3(data.source);
53598
- if (source.kind === "plugin" && source.plugin === "compact" && typeof source.compactionId === "string") {
53619
+ const compacted = source.kind === "compact-checkpoint" || source.kind === "plugin" && source.plugin === "compact";
53620
+ if (compacted && typeof source.compactionId === "string") {
53599
53621
  push3({ role: "event", kind: "compaction", text: summaries.get(source.compactionId) || "\u5BF9\u8BDD\u5DF2\u538B\u7F29", timestamp, rawType: raw2.type });
53600
53622
  }
53601
53623
  } else if (isAppendSurfaceEvent(event)) {
@@ -53609,24 +53631,24 @@ ${answer}`,
53609
53631
  const text9 = visibleText(message.content);
53610
53632
  if (text9.trim()) push3({ role: "assistant", kind: "message", text: text9, timestamp, rawType: raw2.type });
53611
53633
  } else if (event.type === "tool/result") {
53612
- const block = event.data.message.content[0];
53613
- const linked = raw2.sourceEventSeqs?.map((seq) => callsBySeq.get(seq)).filter((value) => value !== void 0 && value.entry.tool?.callId === block.toolCallId) ?? [];
53614
- const pending = linked.length > 1 ? void 0 : linked[0] ?? calls.get(callKey(block.toolCallId));
53615
- if (pending) finishTool(pending, block.content, block.isError === true, event.data.error?.code, timestamp);
53634
+ const result = toolResult(event.data.message);
53635
+ const linked = raw2.sourceEventSeqs?.map((seq) => callsBySeq.get(seq)).filter((value) => value !== void 0 && value.entry.tool?.callId === result.callId) ?? [];
53636
+ const pending = linked.length > 1 ? void 0 : linked[0] ?? calls.get(callKey(result.callId));
53637
+ if (pending) finishTool(pending, result.content, result.isError, event.data.error?.code, timestamp);
53616
53638
  else push3({
53617
53639
  role: "tool",
53618
53640
  kind: "tool",
53619
- text: visibleText(block.content),
53641
+ text: visibleText(result.content),
53620
53642
  timestamp,
53621
53643
  rawType: raw2.type,
53622
- tool: { callId: block.toolCallId, result: { type: block.isError ? "failure" : "success", log: visibleText(block.content) } }
53644
+ tool: { callId: result.callId, result: { type: result.isError ? "failure" : "success", log: visibleText(result.content) } }
53623
53645
  });
53624
53646
  } else {
53625
53647
  diagnostics.ignored++;
53626
53648
  continue;
53627
53649
  }
53628
53650
  } else {
53629
- if (KNOWN_SESSION_EVENT_TYPES.has(raw2.type)) diagnostics.ignored++;
53651
+ if (isKnownDshEventType(raw2.type)) diagnostics.ignored++;
53630
53652
  else {
53631
53653
  diagnostics.unknown++;
53632
53654
  unknown2.add(raw2.type);
@@ -53651,6 +53673,18 @@ ${answer}`,
53651
53673
  function record3(value) {
53652
53674
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
53653
53675
  }
53676
+ function toolResult(message) {
53677
+ const row2 = record3(message);
53678
+ if (row2.role === "tool") {
53679
+ return { callId: String(row2.toolCallId), content: row2.content, isError: row2.isError === true };
53680
+ }
53681
+ const wrapper = record3(Array.isArray(row2.content) ? row2.content[0] : void 0);
53682
+ return { callId: String(wrapper.toolCallId), content: wrapper.content, isError: wrapper.isError === true };
53683
+ }
53684
+ var V4_EVENT_TYPES = /* @__PURE__ */ new Set(["developer/message", "workspace/changes"]);
53685
+ function isKnownDshEventType(type) {
53686
+ return KNOWN_SESSION_EVENT_TYPES.has(type) || V4_EVENT_TYPES.has(type);
53687
+ }
53654
53688
  function parseJson(value) {
53655
53689
  if (typeof value !== "string") return value;
53656
53690
  try {
@@ -54767,13 +54801,25 @@ async function findSession(id, agents = AGENTS, roots = {}) {
54767
54801
  return findSessionAmong(await discoverSessions(agents, roots), id);
54768
54802
  }
54769
54803
  function findSessionAmong(refs, id) {
54770
- return refs.find((session) => session.id === id) ?? refs.find((session) => session.id.startsWith(id));
54804
+ for (const needle of id.startsWith("session-") ? [id] : [id, `session-${id}`]) {
54805
+ const found = refs.find((session) => session.id === needle) ?? refs.find((session) => session.id.startsWith(needle));
54806
+ if (found) return found;
54807
+ }
54808
+ return void 0;
54771
54809
  }
54772
54810
  async function searchRefs(refs, query, limit = 20) {
54773
54811
  const hits = [];
54774
54812
  const needle = query.toLowerCase();
54813
+ let skipped = 0;
54775
54814
  for (const ref of refs) {
54776
- const parsed = await parseSession(ref);
54815
+ let parsed;
54816
+ try {
54817
+ parsed = await parseSession(ref);
54818
+ } catch (error) {
54819
+ skipped++;
54820
+ console.error(`\u8DF3\u8FC7\u65E0\u6CD5\u89E3\u6790\u7684\u4F1A\u8BDD ${ref.path}\uFF1A${error instanceof Error ? error.message : String(error)}`);
54821
+ continue;
54822
+ }
54777
54823
  for (const entry of parsed.entries) {
54778
54824
  const searchText = timelineEntrySearchText(entry);
54779
54825
  if (!searchText.toLowerCase().includes(needle)) continue;
@@ -54796,6 +54842,7 @@ async function searchRefs(refs, query, limit = 20) {
54796
54842
  if (hits.length >= limit) return hits;
54797
54843
  }
54798
54844
  }
54845
+ if (skipped > 0) console.error(`\u5DF2\u8DF3\u8FC7 ${skipped} \u4E2A\u65E0\u6CD5\u89E3\u6790\u7684\u4F1A\u8BDD\uFF08\u8BE6\u89C1\u4E0A\u65B9\u5404\u6761\u8DEF\u5F84\u4E0E\u539F\u56E0\uFF09`);
54799
54846
  return hits;
54800
54847
  }
54801
54848
  async function summarizeSession(ref) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asmgr",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Search and render coding-agent sessions and public ChatGPT shares as self-contained HTML or Markdown.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,6 +30,7 @@
30
30
  "@deepseek-ai/dsh-session": "0.1.5-rc.1",
31
31
  "@deepseek-ai/dsh-session-format": "0.1.5-rc.1",
32
32
  "@deepseek-ai/dsh-session-format-catalog": "0.1.5-rc.1",
33
+ "@deepseek-ai/dsh-session-format-v2-to-v3": "0.1.5-rc.1",
33
34
  "@semantic-release/changelog": "^6.0.3",
34
35
  "@semantic-release/exec": "^7.0.3",
35
36
  "@semantic-release/git": "^10.0.1",