oh-my-knowledge 0.53.0 → 0.54.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.
@@ -5,7 +5,7 @@ import { OMK_HOME } from '../eval-core/default-dirs.js';
5
5
  import { isReportFileName, randomRunToken, reportFilePath } from '../eval-core/artifact-file-names.js';
6
6
  import { migrateLegacyReportFiles } from '../eval-core/report-file-migration.js';
7
7
  import { extractGapSignalsFromTrace } from '../analysis/gap-analyzer.js';
8
- import { loadTraceSessions, tracesToResultEntries, skillSegmentTimestampObserved, } from './trace-adapter.js';
8
+ import { loadTraceSessions, segmentTraceBySkill, tracesToResultEntries, skillSegmentTimestampObserved, } from './trace-adapter.js';
9
9
  import { normalizeTraceTimestamp } from './trace-ir.js';
10
10
  import { isSearchToolCall, toolCallQuery } from '../shared/tool-search.js';
11
11
  import { isToolCallFailure, isToolCallSuccess } from '../shared/tool-call-status.js';
@@ -410,7 +410,12 @@ function skillSessionCountKey(segment) {
410
410
  * `buildObserveDiagnosticsFromReport(report)` 写入 `report.diagnostics`。
411
411
  */
412
412
  export function buildObservationInboxReport(tracePath, options = {}) {
413
- const { sessions, segments, ingestion } = tracesToResultEntries(tracePath);
413
+ const { sessions, ingestion } = tracesToResultEntries(tracePath);
414
+ return buildObservationInboxReportFromTraceSessions(tracePath, sessions, ingestion, options);
415
+ }
416
+ /** Build the existing inbox artifact from an in-memory source-neutral corpus. */
417
+ export function buildObservationInboxReportFromTraceSessions(tracePath, sessions, ingestion, options = {}) {
418
+ const segments = sessions.flatMap(segmentTraceBySkill);
414
419
  const skillSegments = segments.filter((segment) => segment.skillName !== 'general');
415
420
  const generatedAt = new Date().toISOString();
416
421
  const sessionTimeRanges = buildSessionTimeRanges(sessions);
@@ -60,8 +60,8 @@ export interface TraceUsageEvent extends TraceEventBase {
60
60
  model?: string;
61
61
  inputTokens: number;
62
62
  outputTokens: number;
63
- cacheReadTokens: number;
64
- cacheCreationTokens: number;
63
+ cacheReadTokens?: number;
64
+ cacheCreationTokens?: number;
65
65
  reasoningTokens?: number;
66
66
  }
67
67
  export interface TraceModelActivityEvent extends TraceEventBase {
@@ -74,7 +74,7 @@ export interface TraceModelActivityEvent extends TraceEventBase {
74
74
  }
75
75
  export interface TraceLifecycleEvent extends TraceEventBase {
76
76
  eventKind: 'lifecycle';
77
- phase: 'session_started' | 'session_ended' | 'turn_started' | 'turn_completed' | 'turn_aborted' | 'turn_interrupted';
77
+ phase: 'session_started' | 'session_ended' | 'turn_started' | 'turn_completed' | 'turn_failed' | 'turn_aborted' | 'turn_interrupted' | 'turn_ended_unknown' | 'step_started' | 'step_completed';
78
78
  reason?: string;
79
79
  durationMs?: number;
80
80
  }
@@ -105,6 +105,9 @@ export interface TraceRuntimeContextEvent extends TraceEventBase {
105
105
  memoryMode?: string;
106
106
  historyMode?: string;
107
107
  contextWindowId?: string;
108
+ parentRunId?: string;
109
+ delegationDepth?: number;
110
+ sourceOrigin?: string;
108
111
  availableTools?: string[];
109
112
  instructions?: string;
110
113
  goal?: string;
@@ -1,8 +1,10 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  const TERMINAL_LIFECYCLE_LABELS = new Set([
3
3
  'turn_completed',
4
+ 'turn_failed',
4
5
  'turn_aborted',
5
6
  'turn_interrupted',
7
+ 'turn_ended_unknown',
6
8
  ]);
7
9
  /** Reconstruct every observable task without consulting Skill attribution. */
8
10
  export function reconstructExperienceTurns(timeline) {
@@ -124,6 +126,8 @@ function turnStatus(events) {
124
126
  const labels = new Set(events.map((event) => event.label));
125
127
  if (labels.has('turn_completed'))
126
128
  return 'completed';
129
+ if (labels.has('turn_failed'))
130
+ return 'failed';
127
131
  if (labels.has('turn_aborted'))
128
132
  return 'aborted';
129
133
  if (labels.has('turn_interrupted'))
@@ -54,8 +54,8 @@ export function renderConversationIndexPage(model, lang = DEFAULT_LANG) {
54
54
  const rows = conversations.map((conversation) => renderConversationRow(conversation, lang)).join('');
55
55
  const content = model.conversations.length > 0
56
56
  ? `<div class="conversation-list" role="list">${rows}</div>`
57
- : `<div class="empty-state"><strong>${zh ? '还没有可浏览的 Codex 对话' : 'No Codex conversations yet'}</strong><span>${zh ? 'Studio 会直接读取 Codex 的本机会话索引,不需要先运行 observe。' : 'Studio reads the local Codex conversation index directly; observe is not required.'}</span></div>`;
58
- return layout(zh ? 'Codex 对话' : 'Codex conversations', `
57
+ : `<div class="empty-state"><strong>${zh ? '还没有可浏览的对话' : 'No conversations yet'}</strong><span>${zh ? '从受支持的 runtime 摄取任务轨迹后,会话会显示在这里。' : 'Sessions appear here after a supported runtime supplies task trajectories.'}</span></div>`;
58
+ return layout(zh ? '对话' : 'Conversations', `
59
59
  <main class="conversation-page conversation-index-app" data-activity-revision="${e(activity.revision)}">
60
60
  <header class="conversation-app-head">
61
61
  <a class="conversation-app-brand" href="/${langQuery}">
@@ -67,7 +67,7 @@ export function renderConversationIndexPage(model, lang = DEFAULT_LANG) {
67
67
  </nav>
68
68
  </header>
69
69
  <div class="conversation-app-body">
70
- <section class="conversation-browser" aria-label="${zh ? 'Codex 对话' : 'Codex conversations'}">
70
+ <section class="conversation-browser" aria-label="${zh ? '对话' : 'Conversations'}">
71
71
  <header class="conversation-toolbar">
72
72
  <div class="conversation-browser-title">
73
73
  <h1>${zh ? '对话' : 'Conversations'}</h1>
@@ -117,7 +117,7 @@ export function renderConversationDetailPage(conversation, lang = DEFAULT_LANG)
117
117
  <header class="conversation-page-head conversation-detail-head">
118
118
  <div>
119
119
  <a class="back-link" href="/conversations${langSuffix}">${zh ? '返回对话总览' : 'Back to conversations'}</a>
120
- <p class="conversation-eyebrow">CODEX · ${e(shortThreadId(conversation.sourceThreadId))}</p>
120
+ <p class="conversation-eyebrow">${e(sourceLabel(conversation))} · ${e(shortThreadId(conversation.sourceThreadId))}</p>
121
121
  <h1>${renderSafeInlineMarkdown(conversation.title)}</h1>
122
122
  <div class="detail-meta">
123
123
  ${conversation.model ? `<span>${e(conversation.model)}</span>` : ''}
@@ -157,8 +157,8 @@ function renderConversationRow(conversation, lang) {
157
157
  ? '<span class="index-pending">—</span>'
158
158
  : `<span><b>${conversation.turnCount}</b>${zh ? '任务' : 'tasks'}</span>`;
159
159
  const activity = openTask
160
- ? `<strong class="running-label"><i aria-hidden="true"></i>${zh ? '进行中' : 'Running'}</strong><span>${e(updated)} · ${e(conversation.model ?? 'Codex')}</span>`
161
- : `<strong>${e(updated)}</strong><span>${e(conversation.model ?? 'Codex')}</span>`;
160
+ ? `<strong class="running-label"><i aria-hidden="true"></i>${zh ? '进行中' : 'Running'}</strong><span>${e(updated)} · ${e(conversation.model ?? sourceLabel(conversation))}</span>`
161
+ : `<strong>${e(updated)}</strong><span>${e(conversation.model ?? sourceLabel(conversation))}</span>`;
162
162
  const liveTaskLink = openTaskHref
163
163
  ? `<a class="live-task-link" href="${e(openTaskHref)}" aria-label="${zh ? '查看进行中任务的实时轨迹' : 'View the live trajectory for the running task'}"><i aria-hidden="true"></i>${zh ? '查看实时轨迹' : 'View live'}</a>`
164
164
  : '';
@@ -345,10 +345,24 @@ function compactPath(value) {
345
345
  function shortThreadId(value) {
346
346
  return value.length > 18 ? `${value.slice(0, 8)}…${value.slice(-6)}` : value;
347
347
  }
348
+ function sourceLabel(conversation) {
349
+ if (conversation.sourceKind === 'dsh')
350
+ return 'DeepSeek Harness';
351
+ if (conversation.sourceKind === 'codex')
352
+ return 'Codex';
353
+ if (conversation.sourceKind === 'claude')
354
+ return 'Claude';
355
+ if (conversation.sourceKind === 'openclaw')
356
+ return 'OpenClaw';
357
+ if (conversation.sourceKind === 'markdown_log')
358
+ return 'Markdown';
359
+ return 'Runtime';
360
+ }
348
361
  function statusLabel(status, lang) {
349
362
  const zh = lang === 'zh';
350
363
  const labels = {
351
364
  completed: ['已完成', 'Completed'],
365
+ failed: ['已失败', 'Failed'],
352
366
  aborted: ['已中止', 'Aborted'],
353
367
  interrupted: ['已打断', 'Interrupted'],
354
368
  open: ['进行中', 'Open'],
@@ -1101,6 +1101,7 @@ export function renderKnowledgeDebuggerPage(model, lang = DEFAULT_LANG, options
1101
1101
  syncing: zh ? '同步中' : 'Syncing',
1102
1102
  reconnecting: zh ? '重连中' : 'Reconnecting',
1103
1103
  failed: zh ? '实时更新失败' : 'Live update failed',
1104
+ taskFailed: zh ? '任务已失败' : 'Task failed',
1104
1105
  following: zh ? '跟随中' : 'Following',
1105
1106
  resume: zh ? '跟随最新' : 'Follow latest',
1106
1107
  pending: zh ? '查看更新' : 'View update',
@@ -1456,8 +1457,12 @@ function lifecycleEventLabel(label, lang) {
1456
1457
  session_ended: { zh: '会话结束', en: 'Session ended' },
1457
1458
  turn_started: { zh: '本轮开始', en: 'Turn started' },
1458
1459
  turn_completed: { zh: '本轮完成', en: 'Turn completed' },
1460
+ turn_failed: { zh: '本轮失败', en: 'Turn failed' },
1459
1461
  turn_aborted: { zh: '本轮中止', en: 'Turn aborted' },
1460
1462
  turn_interrupted: { zh: '本轮被打断', en: 'Turn interrupted' },
1463
+ turn_ended_unknown: { zh: '本轮结束状态未知', en: 'Turn ended with unknown status' },
1464
+ step_started: { zh: '步骤开始', en: 'Step started' },
1465
+ step_completed: { zh: '步骤完成', en: 'Step completed' },
1461
1466
  };
1462
1467
  return labels[label ?? '']?.[lang] ?? (label || (lang === 'zh' ? '运行状态变化' : 'Lifecycle event'));
1463
1468
  }
@@ -1466,7 +1471,7 @@ function lifecycleMilestoneTone(label) {
1466
1471
  return 'start';
1467
1472
  if (label === 'session_ended' || label === 'turn_completed')
1468
1473
  return 'end';
1469
- if (label === 'turn_aborted' || label === 'turn_interrupted')
1474
+ if (label === 'turn_failed' || label === 'turn_aborted' || label === 'turn_interrupted')
1470
1475
  return 'warning';
1471
1476
  return 'neutral';
1472
1477
  }
@@ -188,8 +188,8 @@ export function renderObservationInboxPage(model, lang = DEFAULT_LANG) {
188
188
  </div>`;
189
189
  };
190
190
  const renderSourceBadge = (item) => {
191
- const label = item.sourceKind === 'openclaw' ? 'OpenClaw' : item.sourceKind === 'codex' ? 'Codex' : item.sourceKind === 'markdown_log' ? 'Markdown log' : item.sourceKind === 'claude' ? 'Claude' : 'Unknown';
192
- const color = item.sourceKind === 'openclaw' ? '#7c3aed' : item.sourceKind === 'codex' ? '#1677ff' : item.sourceKind === 'markdown_log' ? 'var(--green)' : item.sourceKind === 'claude' ? 'var(--accent)' : 'var(--text-muted)';
191
+ const label = item.sourceKind === 'dsh' ? 'DeepSeek Harness' : item.sourceKind === 'openclaw' ? 'OpenClaw' : item.sourceKind === 'codex' ? 'Codex' : item.sourceKind === 'markdown_log' ? 'Markdown log' : item.sourceKind === 'claude' ? 'Claude' : 'Unknown';
192
+ const color = item.sourceKind === 'dsh' ? '#0f766e' : item.sourceKind === 'openclaw' ? '#7c3aed' : item.sourceKind === 'codex' ? '#1677ff' : item.sourceKind === 'markdown_log' ? 'var(--green)' : item.sourceKind === 'claude' ? 'var(--accent)' : 'var(--text-muted)';
193
193
  return `<span title="调用日志来源:${e(label)}" style="display:inline-flex;margin-top:4px;padding:2px 6px;border-radius:999px;background:var(--bg-muted);color:${color};font-size:11px;font-weight:650">${e(label)}</span>`;
194
194
  };
195
195
  const confidenceHeaderHelp = '判断把握:OMK 对“这条 过程发现 是否需要处理/是否高风险/需关注”的规则判断有多确定。归属把握:OMK 把这条 过程发现 归到当前 skill 名下有多确定,例如明确调用 skill 通常更高。';
@@ -4,6 +4,7 @@ export interface TrajectoryLiveLabels {
4
4
  syncing: string;
5
5
  reconnecting: string;
6
6
  failed: string;
7
+ taskFailed: string;
7
8
  following: string;
8
9
  resume: string;
9
10
  pending: string;
@@ -48,7 +48,9 @@ export function createTrajectoryLiveController(options) {
48
48
  const state = terminalState ?? (!followLatest && pendingRevision
49
49
  ? 'pending'
50
50
  : (followLatest ? 'following' : 'paused'));
51
- const terminalLabel = terminalState ? labels[terminalState] : undefined;
51
+ const terminalLabel = terminalState
52
+ ? terminalState === 'failed' ? labels.taskFailed : labels[terminalState]
53
+ : undefined;
52
54
  followButton.dataset.state = state;
53
55
  followButton.dataset.following = String(followLatest);
54
56
  followButton.setAttribute('aria-pressed', String(followLatest));
@@ -212,6 +214,7 @@ export function createTrajectoryLiveController(options) {
212
214
  try {
213
215
  const update = JSON.parse(event.data);
214
216
  const explicitTerminal = update.status === 'completed'
217
+ || update.status === 'failed'
215
218
  || update.status === 'aborted'
216
219
  || update.status === 'interrupted';
217
220
  const streamTerminal = update.liveObservable === false || explicitTerminal;
@@ -226,7 +229,7 @@ export function createTrajectoryLiveController(options) {
226
229
  if (!update.revision || update.revision === shell.dataset.liveRevision) {
227
230
  const quiet = update.status === 'unknown';
228
231
  const connectionLabel = terminalState
229
- ? labels[terminalState]
232
+ ? terminalState === 'failed' ? labels.taskFailed : labels[terminalState]
230
233
  : quiet ? labels.reconnecting : labels.live;
231
234
  setConnectionState(terminalState ?? (quiet ? 'reconnecting' : 'live'), connectionLabel);
232
235
  updateFollowControl();
@@ -1,6 +1,7 @@
1
1
  const TRACE_SOURCE_KINDS = new Set([
2
2
  'claude',
3
3
  'codex',
4
+ 'dsh',
4
5
  'openclaw',
5
6
  'markdown_log',
6
7
  'unknown',
@@ -381,7 +381,7 @@ export type DebugKnowledgeAccessKind = 'injected' | 'read' | 'returned';
381
381
  export type TaskReplayStepKind = 'user_request' | 'user_message' | 'user_correction' | 'runtime_context' | 'skill_context' | 'tool_exchange' | 'unmatched_tool_result' | 'assistant_message' | 'model_activity' | 'lifecycle' | 'observation' | 'system_event';
382
382
  export type TaskReplayIntegrityCode = 'task_boundary_unavailable' | 'timeline_truncated' | 'malformed_records' | 'ignored_values' | 'unknown_events' | 'unmatched_tool_calls' | 'unmatched_tool_results' | 'missing_timestamps';
383
383
  export type TaskWindowBasis = 'turn_id' | 'turn_lifecycle' | 'user_message' | 'unresolved';
384
- export type ExperienceTurnStatus = 'completed' | 'aborted' | 'interrupted' | 'open' | 'unknown';
384
+ export type ExperienceTurnStatus = 'completed' | 'failed' | 'aborted' | 'interrupted' | 'open' | 'unknown';
385
385
  /**
386
386
  * One user-visible task inside a source thread. `turnId` is the stable,
387
387
  * source-neutral identity used by Studio routes. `sourceTurnId` preserves the
@@ -1,2 +1,2 @@
1
1
  /** Stable source identity shared by execution evidence and Trace IR. */
2
- export type TraceSourceKind = 'claude' | 'codex' | 'openclaw' | 'markdown_log' | 'unknown';
2
+ export type TraceSourceKind = 'claude' | 'codex' | 'dsh' | 'openclaw' | 'markdown_log' | 'unknown';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-knowledge",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "packageManager": "yarn@4.16.0",
5
5
  "description": "OMK — Observe. Measure. Know. Evidence-backed knowledge changes for AI applications.",
6
6
  "type": "module",
@@ -85,6 +85,10 @@
85
85
  "release-evidence",
86
86
  "llm-observability",
87
87
  "prompt-engineering",
88
+ "deepseek-harness",
89
+ "dsh-plugin",
90
+ "dsh",
91
+ "cordis",
88
92
  "codex",
89
93
  "codex-cli",
90
94
  "openai",