dsh-activity-pane 0.2.2 → 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/src/core.mjs CHANGED
@@ -1067,6 +1067,31 @@ export function usageSummary({ uncachedInputTokens = null, cacheReadTokens = nul
1067
1067
  return { inputTokens, cacheHitPct };
1068
1068
  }
1069
1069
 
1070
+ /** 从 sessions.list 的投影统一归一卡片统计:运行卡与最近卡共享同一口径。 */
1071
+ export function statsFromProjection(projection = null, elapsedMs = null) {
1072
+ const usage = isRecord(projection?.tokenUsage) ? projection.tokenUsage : {};
1073
+ const sessionStats = isRecord(projection?.sessionStats) ? projection.sessionStats : {};
1074
+ const rateTokS = Number.isFinite(sessionStats.decodeMs) && sessionStats.decodeMs > 0
1075
+ ? sessionStats.decodeTokens / (sessionStats.decodeMs / 1000)
1076
+ : null;
1077
+ return {
1078
+ ...runtimeStats({ elapsedMs, outputTokens: usage.outputTokens, rateTokS }),
1079
+ ...usageSummary(usage),
1080
+ };
1081
+ }
1082
+
1083
+ /** 统计字段按字段合并:当前值有效时优先,否则回退到上一份已知值;不合并 elapsedMs。 */
1084
+ export function mergeRuntimeStats(current = null, fallback = null) {
1085
+ const currentStats = isRecord(current) ? current : {};
1086
+ const fallbackStats = isRecord(fallback) ? fallback : {};
1087
+ return {
1088
+ outputTokens: currentStats.outputTokens ?? fallbackStats.outputTokens ?? null,
1089
+ inputTokens: currentStats.inputTokens ?? fallbackStats.inputTokens ?? null,
1090
+ cacheHitPct: currentStats.cacheHitPct ?? fallbackStats.cacheHitPct ?? null,
1091
+ rateTokS: currentStats.rateTokS ?? fallbackStats.rateTokS ?? null,
1092
+ };
1093
+ }
1094
+
1070
1095
  /** 需要用户行动的种类的展示文案。 */
1071
1096
  export function pendingText(kind) {
1072
1097
  return PENDING_LABELS[kind] ?? PENDING_UNKNOWN_LABEL;
@@ -1168,8 +1193,8 @@ export function escapeCssString(value) {
1168
1193
  * windowComplete(R-01-009/AC-06、R-01-012/AC-12 冷窗口兜底):快照已就绪但窗口缺
1169
1194
  * 锚点数据(开放回合起点或可锚用户行在窗口外)时为 false——此时仍发起一次 history
1170
1195
  * 补读,供进度锚点与指令锚行兜底。previewFallbackNeeded 表示最近卡的快照预览
1171
- * 不完整,同样补读一次 history(R-01-013/AC-03、AC-04);durationFallbackNeeded 表示等待卡
1172
- * 需要在已加载的旧 history 之后再取一次最新回合边界(R-01-009/AC-12)。 */
1196
+ * 不完整,同样补读一次 history(R-01-013/AC-03、AC-04);durationFallbackNeeded 表示等待卡或最近卡
1197
+ * 需要在已加载的旧 history 之后再取一次最新回合边界(R-01-009/AC-12、R-01-013/AC-12)。 */
1173
1198
  export function detailLoadPlan({
1174
1199
  detail = {},
1175
1200
  isSubagent = false,
@@ -1594,11 +1619,6 @@ export function listLoadState(snapshot) {
1594
1619
  return "ready";
1595
1620
  }
1596
1621
 
1597
- /** 历史窗口:会话最后一次活动距现在不超过该毫秒数则视为"最近使用过"。 */
1598
- export const HISTORY_WINDOW_MS = 24 * 60 * 60 * 1000;
1599
- /** 历史区最多展示的最近会话条数。 */
1600
- export const HISTORY_MAX = 20;
1601
-
1602
1622
  /** 会话行是否为某主会话的直属子代理。 */
1603
1623
  export function isSubagentRow(row, byId = {}) {
1604
1624
  const id = row?.parentId;
@@ -1702,7 +1722,7 @@ function entryErrorNote(completion) {
1702
1722
 
1703
1723
  /**
1704
1724
  * 活动区→历史区迁移检测(R-01-010/AC-07):上一帧活动区 id 在本帧离开活动区且出现于
1705
- * 历史区即判定为一次迁移;彻底消失(归档、滑出历史窗口)不判定。prevActiveIds 为上一帧
1725
+ * 历史区即判定为一次迁移;彻底消失(归档、不再被会话服务列出)不判定。prevActiveIds 为上一帧
1706
1726
  * 已渲染的活动区 id 集合,active/recent 为本帧派生条目。
1707
1727
  */
1708
1728
  export function movedToRecentIds(prevActiveIds, active, recent) {
@@ -1718,7 +1738,7 @@ export function movedToRecentIds(prevActiveIds, active, recent) {
1718
1738
 
1719
1739
  /**
1720
1740
  * 历史区→活动区迁移检测(R-01-010/AC-07):上一帧历史区 id 在本帧离开历史区且出现于
1721
- * 活动区即判定为一次反向迁移;彻底消失(归档、滑出历史窗口)不判定。prevRecentIds 为上一帧
1741
+ * 活动区即判定为一次反向迁移;彻底消失(归档、不再被会话服务列出)不判定。prevRecentIds 为上一帧
1722
1742
  * 已渲染的历史区 id 集合,active/recent 为本帧派生条目。与 movedToRecentIds 镜像对称。
1723
1743
  */
1724
1744
  export function movedToActiveIds(prevRecentIds, active, recent) {
@@ -1818,16 +1838,12 @@ export function lastTurnDuration({ turnTimings = null, history = [] } = {}) {
1818
1838
  }
1819
1839
 
1820
1840
  /**
1821
- * 构建最近历史区条目:当前非活动、且在历史窗口内最后一次活动过的**主会话**
1822
- * (子代理是临时工作单元,不入最近历史;故需同时排除表白会话与已结束子代理),
1823
- * 按最后活动时间从新到旧,最多 HISTORY_MAX 条。blank 会话不出现(从未用过);
1824
- * 归档会话不出现——原生 runtime 会立即清空对归档会话的选中,列出它只会得到
1825
- * 一张点了回落到新会话界面的死卡。完成确认中的会话留在活动区,不入历史区;
1826
- * 委托周期(含耗尽空窗)中的会话同样留在活动区(delegatingIds,分区不变量)。
1827
- * 窗口候选判定用宿主列表时间(下界);turnEnds(id → 已知回合结束时刻)驱动
1828
- * 时间精化:条目 activityAt 取宿主列表时间与回合结束时刻的较新者(R-01-010/AC-08、AC-09)。
1841
+ * 构建历史区条目:当前非活动的**主会话**,按最后活动时间从新到旧返回完整候选集合。
1842
+ * 子代理是临时工作单元,不入历史区;归档、空会话、完成/错误提醒与委托周期中的会话
1843
+ * 也不入历史区。历史区不再按时间窗口或条数截断;turnEnds(id → 已知回合结束时刻)驱动
1844
+ * activityAt 精化(R-01-010、R-01-019)。
1829
1845
  */
1830
- export function buildRecent(snapshot, workspaceItems, now, windowMs = HISTORY_WINDOW_MS, detailsById = {}, archivedIds = [], completions = null, delegatingIds = null, turnEnds = null) {
1846
+ export function buildRecent(snapshot, workspaceItems, now, detailsById = {}, archivedIds = [], completions = null, delegatingIds = null, turnEnds = null) {
1831
1847
  const byId = isRecord(snapshot) && isRecord(snapshot.byId) ? snapshot.byId : {};
1832
1848
  const ids = Array.isArray(snapshot?.ids) ? snapshot.ids : [];
1833
1849
  const current = snapshot?.current ?? null;
@@ -1848,7 +1864,7 @@ export function buildRecent(snapshot, workspaceItems, now, windowMs = HISTORY_WI
1848
1864
  if (isActiveRow(row, byId, activeIds)) continue;
1849
1865
  const updatedAt = Number(row.updatedAt);
1850
1866
  if (!Number.isFinite(updatedAt)) continue;
1851
- if (updatedAt > now || now - updatedAt > windowMs) continue;
1867
+ if (Number.isFinite(now) && updatedAt > now) continue;
1852
1868
  const turnEnd = Number(mapValue(turnEnds, id));
1853
1869
  const activityAt = Number.isFinite(turnEnd) ? Math.max(updatedAt, turnEnd) : updatedAt;
1854
1870
  const details = mapValue(detailsById, id) ?? {};
@@ -1872,8 +1888,14 @@ export function buildRecent(snapshot, workspaceItems, now, windowMs = HISTORY_WI
1872
1888
  });
1873
1889
  }
1874
1890
 
1875
- entries.sort((a, b) => b.activityAt - a.activityAt);
1876
- return entries.slice(0, HISTORY_MAX);
1891
+ entries.sort((a, b) => {
1892
+ const byActivity = b.activityAt - a.activityAt;
1893
+ if (byActivity !== 0) return byActivity;
1894
+ const aId = String(a.id);
1895
+ const bId = String(b.id);
1896
+ return aId < bId ? -1 : aId > bId ? 1 : 0;
1897
+ });
1898
+ return entries;
1877
1899
  }
1878
1900
 
1879
1901
  /** 毫秒时长的人性化短格式,例如 "47s"、"3m12s"。 */
@@ -1884,6 +1906,41 @@ export function fmtElapsedMs(ms) {
1884
1906
  return `${Math.floor(s / 60)}m${s % 60}s`;
1885
1907
  }
1886
1908
 
1909
+ /** 历史卡相对活动时间:按分钟、小时、天、周、月、年分级;负值返回空。 */
1910
+ export function fmtRelativeAge(ageMs) {
1911
+ if (!Number.isFinite(ageMs) || ageMs < 0) return "";
1912
+ const minute = 60_000;
1913
+ const hour = 60 * minute;
1914
+ const day = 24 * hour;
1915
+ if (ageMs < minute) return "刚刚";
1916
+ if (ageMs < hour) return `${Math.floor(ageMs / minute)}分钟前`;
1917
+ if (ageMs < day) return `${Math.floor(ageMs / hour)}小时前`;
1918
+ if (ageMs < 7 * day) return `${Math.floor(ageMs / day)}天前`;
1919
+ if (ageMs < 30 * day) return `${Math.floor(ageMs / (7 * day))}周前`;
1920
+ if (ageMs < 365 * day) return `${Math.floor(ageMs / (30 * day))}个月前`;
1921
+ return `${Math.floor(ageMs / (365 * day))}年前`;
1922
+ }
1923
+
1924
+ /** 历史卡绝对活动时间:使用浏览器/宿主本地时区,当前年份省略年份,跨年补年份。 */
1925
+ export function fmtAbsoluteDateTime(ts, now = Date.now()) {
1926
+ if (ts === null || ts === undefined || now === null || now === undefined) return "";
1927
+ try {
1928
+ const value = Number(ts);
1929
+ const current = Number(now);
1930
+ const date = new Date(value);
1931
+ const today = new Date(current);
1932
+ if (!Number.isFinite(value) || !Number.isFinite(current) || !Number.isFinite(date.getTime()) || !Number.isFinite(today.getTime())) return "";
1933
+ const dateOptions = { month: "2-digit", day: "2-digit" };
1934
+ if (date.getFullYear() !== today.getFullYear()) dateOptions.year = "numeric";
1935
+ return `${date.toLocaleDateString([], dateOptions)} ${date.toLocaleTimeString([], {
1936
+ hour: "2-digit",
1937
+ minute: "2-digit",
1938
+ })}`;
1939
+ } catch {
1940
+ return "";
1941
+ }
1942
+ }
1943
+
1887
1944
  /** token 计数的人性化短格式,例如 "847"、"1.2k";非有限非负时返回 null。 */
1888
1945
  /** token 计数紧凑缩写,镜像原生统计行 formatTokens:847 / 12.2K / 517K / 2.8M——
1889
1946
  * 千以下原样;K/M 档缩写值百位以上取整、不足百位保留一位小数;非法输入返回 null 不展示。 */