oh-my-knowledge 0.48.0 → 0.49.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.
Files changed (243) hide show
  1. package/README.md +50 -18
  2. package/README.zh.md +55 -23
  3. package/dist/analysis/coverage-analyzer.d.ts +1 -0
  4. package/dist/analysis/coverage-analyzer.js +125 -62
  5. package/dist/analysis/failure-clusterer.js +2 -1
  6. package/dist/analysis/gap-analyzer.d.ts +2 -2
  7. package/dist/analysis/gap-analyzer.js +13 -3
  8. package/dist/analysis/hedging-classifier.d.ts +2 -2
  9. package/dist/analysis/hedging-classifier.js +3 -4
  10. package/dist/analysis/report-diagnostics.js +9 -7
  11. package/dist/analysis/sample-diagnostics.js +6 -6
  12. package/dist/artifact-graph/doctor.js +15 -7
  13. package/dist/assets/agent-skills/omk/SKILL.md +27 -7
  14. package/dist/assets/agent-skills/omk/references/commands.md +18 -17
  15. package/dist/authoring/evolver.d.ts +10 -6
  16. package/dist/authoring/evolver.js +496 -83
  17. package/dist/authoring/generator.d.ts +3 -3
  18. package/dist/authoring/generator.js +5 -10
  19. package/dist/authoring/sample-fixer.d.ts +8 -6
  20. package/dist/authoring/sample-fixer.js +76 -5
  21. package/dist/cli/commands/doctor.js +31 -14
  22. package/dist/cli/commands/eval/index.d.ts +3 -0
  23. package/dist/cli/commands/eval/index.js +163 -17
  24. package/dist/cli/commands/evolve.d.ts +4 -4
  25. package/dist/cli/commands/evolve.js +27 -13
  26. package/dist/cli/commands/init.js +16 -3
  27. package/dist/cli/commands/observe/inbox.js +28 -21
  28. package/dist/cli/commands/observe/index.js +20 -11
  29. package/dist/cli/commands/observe/ingest.d.ts +3 -0
  30. package/dist/cli/commands/observe/ingest.js +30 -2
  31. package/dist/cli/commands/sample.d.ts +6 -3
  32. package/dist/cli/commands/sample.js +72 -68
  33. package/dist/cli/lib/codex-model-hint.d.ts +9 -0
  34. package/dist/cli/lib/codex-model-hint.js +45 -0
  35. package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
  36. package/dist/cli/lib/generation-failure-hint.js +61 -0
  37. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  38. package/dist/cli/lib/i18n-dict/common.js +4 -0
  39. package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
  40. package/dist/cli/lib/i18n-dict/gen.js +38 -6
  41. package/dist/cli/lib/i18n-dict/help.js +6 -6
  42. package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
  43. package/dist/cli/lib/i18n-dict/init.js +13 -9
  44. package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
  45. package/dist/cli/lib/i18n-dict/run.js +34 -2
  46. package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
  47. package/dist/cli/lib/llm-failure-classifier.js +8 -0
  48. package/dist/cli/lib/parse-run-config.d.ts +6 -5
  49. package/dist/cli/lib/parse-run-config.js +16 -9
  50. package/dist/cli/lib/runtime-defaults.d.ts +21 -0
  51. package/dist/cli/lib/runtime-defaults.js +79 -0
  52. package/dist/diagnosis/observe-mapper.js +14 -15
  53. package/dist/diagnosis/observe-producer.js +3 -1
  54. package/dist/diagnosis/studio-projection.js +14 -7
  55. package/dist/diagnosis/types.d.ts +2 -0
  56. package/dist/diagnosis/types.js +12 -0
  57. package/dist/doctor/endpoint-rule.js +2 -1
  58. package/dist/eval-core/artifact-file-names.js +18 -1
  59. package/dist/eval-core/artifact-index.d.ts +7 -11
  60. package/dist/eval-core/artifact-index.js +139 -80
  61. package/dist/eval-core/cache.d.ts +12 -3
  62. package/dist/eval-core/cache.js +89 -29
  63. package/dist/eval-core/comparability.js +10 -6
  64. package/dist/eval-core/evaluation-execution.d.ts +2 -1
  65. package/dist/eval-core/evaluation-execution.js +122 -37
  66. package/dist/eval-core/evaluation-job.d.ts +4 -1
  67. package/dist/eval-core/evaluation-job.js +4 -1
  68. package/dist/eval-core/evaluation-reporting.d.ts +15 -13
  69. package/dist/eval-core/evaluation-reporting.js +54 -52
  70. package/dist/eval-core/execution-strategy.d.ts +2 -0
  71. package/dist/eval-core/execution-strategy.js +11 -9
  72. package/dist/eval-core/fact-checker.js +15 -7
  73. package/dist/eval-core/holdout.js +3 -2
  74. package/dist/eval-core/judge-independence.d.ts +2 -2
  75. package/dist/eval-core/mock-hook.cjs +23 -6
  76. package/dist/eval-core/mocks-runtime.js +30 -8
  77. package/dist/eval-core/report-document.d.ts +12 -0
  78. package/dist/eval-core/report-document.js +1151 -0
  79. package/dist/eval-core/report-extensions.d.ts +4 -0
  80. package/dist/eval-core/report-extensions.js +500 -0
  81. package/dist/eval-core/report-file-migration.js +7 -2
  82. package/dist/eval-core/resume-compatibility.d.ts +31 -0
  83. package/dist/eval-core/resume-compatibility.js +141 -0
  84. package/dist/eval-core/sample-fingerprint.d.ts +12 -0
  85. package/dist/eval-core/sample-fingerprint.js +193 -0
  86. package/dist/eval-core/schema.js +86 -31
  87. package/dist/eval-core/verdict.d.ts +8 -4
  88. package/dist/eval-core/verdict.js +24 -10
  89. package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
  90. package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
  91. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
  92. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
  93. package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
  94. package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
  95. package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
  96. package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
  97. package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
  98. package/dist/eval-workflows/evaluation-pipeline.js +12 -7
  99. package/dist/eval-workflows/run-evaluation.d.ts +9 -7
  100. package/dist/eval-workflows/run-evaluation.js +79 -51
  101. package/dist/executors/anthropic-api.js +65 -9
  102. package/dist/executors/claude-cli.js +16 -79
  103. package/dist/executors/claude-protocol.d.ts +28 -0
  104. package/dist/executors/claude-protocol.js +180 -0
  105. package/dist/executors/claude-sdk-trace.js +56 -28
  106. package/dist/executors/claude-sdk.d.ts +1 -0
  107. package/dist/executors/claude-sdk.js +39 -93
  108. package/dist/executors/codex-cli-trace.js +166 -31
  109. package/dist/executors/codex-cli.d.ts +6 -8
  110. package/dist/executors/codex-cli.js +49 -151
  111. package/dist/executors/codex-protocol.d.ts +24 -0
  112. package/dist/executors/codex-protocol.js +234 -0
  113. package/dist/executors/codex-sdk.js +68 -120
  114. package/dist/executors/gemini.js +88 -13
  115. package/dist/executors/index.d.ts +2 -3
  116. package/dist/executors/index.js +5 -3
  117. package/dist/executors/openai-api.js +70 -9
  118. package/dist/executors/runtime-fingerprint.js +88 -11
  119. package/dist/executors/script-command.d.ts +8 -0
  120. package/dist/executors/script-command.js +87 -0
  121. package/dist/executors/script.js +202 -29
  122. package/dist/executors/shared.d.ts +35 -3
  123. package/dist/executors/shared.js +113 -15
  124. package/dist/grading/assertions.d.ts +1 -1
  125. package/dist/grading/assertions.js +19 -9
  126. package/dist/grading/diagnostic.d.ts +9 -2
  127. package/dist/grading/diagnostic.js +25 -2
  128. package/dist/grading/index.js +10 -4
  129. package/dist/grading/judge.js +19 -6
  130. package/dist/grading/layered-scores.d.ts +2 -3
  131. package/dist/grading/layered-scores.js +2 -3
  132. package/dist/inputs/load-samples.d.ts +1 -2
  133. package/dist/inputs/load-samples.js +23 -1
  134. package/dist/inputs/mcp-resolver.js +6 -3
  135. package/dist/inputs/sample-document.d.ts +11 -0
  136. package/dist/inputs/sample-document.js +96 -0
  137. package/dist/managed/evidence.d.ts +1 -0
  138. package/dist/managed/evidence.js +1 -1
  139. package/dist/managed/store.js +200 -91
  140. package/dist/observability/codex-trace-adapter.d.ts +5 -0
  141. package/dist/observability/codex-trace-adapter.js +850 -0
  142. package/dist/observability/experience.d.ts +32 -6
  143. package/dist/observability/experience.js +2695 -459
  144. package/dist/observability/feedback-matchers.js +16 -1
  145. package/dist/observability/inbox-view-model.d.ts +1 -1
  146. package/dist/observability/inbox-view-model.js +19 -14
  147. package/dist/observability/inbox.d.ts +7 -1
  148. package/dist/observability/inbox.js +632 -124
  149. package/dist/observability/problem-patterns.js +2 -0
  150. package/dist/observability/review-state.d.ts +6 -0
  151. package/dist/observability/review-state.js +235 -63
  152. package/dist/observability/skill-chain-advisories.js +1 -1
  153. package/dist/observability/skill-chain.js +17 -4
  154. package/dist/observability/skill-health-analyzer.d.ts +32 -7
  155. package/dist/observability/skill-health-analyzer.js +194 -121
  156. package/dist/observability/skill-health-report.d.ts +10 -0
  157. package/dist/observability/skill-health-report.js +620 -0
  158. package/dist/observability/soft-standards/constants.d.ts +0 -1
  159. package/dist/observability/soft-standards/constants.js +0 -1
  160. package/dist/observability/soft-standards/index.d.ts +1 -1
  161. package/dist/observability/soft-standards/index.js +1 -1
  162. package/dist/observability/soft-standards/llm-extractor.js +8 -10
  163. package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
  164. package/dist/observability/soft-standards/skill-standards-store.js +59 -18
  165. package/dist/observability/soft-standards/types.d.ts +2 -2
  166. package/dist/observability/trace-adapter.d.ts +12 -7
  167. package/dist/observability/trace-adapter.js +11 -9
  168. package/dist/observability/trace-attribution.d.ts +13 -5
  169. package/dist/observability/trace-attribution.js +315 -21
  170. package/dist/observability/trace-ingestion.d.ts +9 -0
  171. package/dist/observability/trace-ingestion.js +80 -0
  172. package/dist/observability/trace-ir.d.ts +113 -0
  173. package/dist/observability/trace-ir.js +87 -0
  174. package/dist/observability/trace-segmenter.d.ts +19 -6
  175. package/dist/observability/trace-segmenter.js +377 -196
  176. package/dist/observability/trace-session-index.d.ts +19 -0
  177. package/dist/observability/trace-session-index.js +68 -0
  178. package/dist/observability/trace-source.d.ts +12 -4
  179. package/dist/observability/trace-source.js +939 -215
  180. package/dist/renderer/html-renderer.js +37 -6
  181. package/dist/renderer/icons.js +3 -0
  182. package/dist/renderer/observation-inbox-renderer.js +208 -90
  183. package/dist/renderer/skill-detail-renderer.js +452 -109
  184. package/dist/renderer/skill-health-renderer.js +69 -12
  185. package/dist/renderer/summary.js +28 -7
  186. package/dist/renderer/table.js +21 -4
  187. package/dist/renderer/test-view.d.ts +1 -0
  188. package/dist/renderer/test-view.js +44 -9
  189. package/dist/server/indexed-report-store.js +14 -18
  190. package/dist/server/job-store.js +64 -26
  191. package/dist/server/report-server.js +190 -78
  192. package/dist/server/report-store.js +57 -80
  193. package/dist/server/skill-index.js +143 -49
  194. package/dist/server/skill-insights.js +44 -5
  195. package/dist/shared/artifact-graph.d.ts +3 -0
  196. package/dist/shared/artifact-graph.js +224 -0
  197. package/dist/shared/assertion-types.d.ts +8 -0
  198. package/dist/shared/assertion-types.js +46 -0
  199. package/dist/shared/atomic-json.d.ts +8 -0
  200. package/dist/shared/atomic-json.js +33 -0
  201. package/dist/shared/diagnosis-schema.d.ts +9 -0
  202. package/dist/shared/diagnosis-schema.js +181 -0
  203. package/dist/shared/doctor-report.d.ts +3 -0
  204. package/dist/shared/doctor-report.js +103 -0
  205. package/dist/shared/evaluation-job.d.ts +6 -0
  206. package/dist/shared/evaluation-job.js +217 -0
  207. package/dist/shared/executor-result.d.ts +17 -0
  208. package/dist/shared/executor-result.js +221 -0
  209. package/dist/shared/file-lock.d.ts +12 -0
  210. package/dist/shared/file-lock.js +129 -0
  211. package/dist/shared/json-value.d.ts +5 -0
  212. package/dist/shared/json-value.js +36 -0
  213. package/dist/shared/keyed-mutex.d.ts +7 -0
  214. package/dist/shared/keyed-mutex.js +24 -0
  215. package/dist/shared/record-count.d.ts +8 -0
  216. package/dist/shared/record-count.js +43 -0
  217. package/dist/shared/sample-contract.d.ts +3 -0
  218. package/dist/shared/sample-contract.js +332 -0
  219. package/dist/shared/timestamp.d.ts +6 -0
  220. package/dist/shared/timestamp.js +64 -0
  221. package/dist/shared/token-usage.d.ts +19 -0
  222. package/dist/shared/token-usage.js +50 -0
  223. package/dist/shared/tool-call-status.d.ts +8 -0
  224. package/dist/shared/tool-call-status.js +28 -0
  225. package/dist/shared/tool-identity.d.ts +21 -0
  226. package/dist/shared/tool-identity.js +84 -0
  227. package/dist/shared/tool-search.js +73 -16
  228. package/dist/shared/trace-projection.d.ts +5 -0
  229. package/dist/shared/trace-projection.js +20 -0
  230. package/dist/shared/trace-source-kind.d.ts +3 -0
  231. package/dist/shared/trace-source-kind.js +12 -0
  232. package/dist/types/diagnosis.d.ts +2 -0
  233. package/dist/types/eval.d.ts +4 -0
  234. package/dist/types/executor.d.ts +32 -5
  235. package/dist/types/index.d.ts +1 -0
  236. package/dist/types/index.js +1 -0
  237. package/dist/types/judge.d.ts +2 -0
  238. package/dist/types/observability.d.ts +116 -9
  239. package/dist/types/report.d.ts +58 -6
  240. package/dist/types/skill-index.d.ts +7 -0
  241. package/dist/types/trace.d.ts +2 -0
  242. package/dist/types/trace.js +1 -0
  243. package/package.json +9 -5
@@ -1,3 +1,5 @@
1
+ import { safeSliceForJson } from '../util/safe-slice.js';
2
+ import { normalizeToolIdentity } from '../shared/tool-identity.js';
1
3
  // Codex CLI(codex 0.125)`exec --json` 事件流 → omk trace 抽取器。
2
4
  // 跟 claude-sdk-trace.ts 不同源:
3
5
  // Claude SDK 是 message[block] 嵌套结构,有 tool_use / tool_result 配对;
@@ -7,45 +9,134 @@
7
9
  // schema(基于 codex 0.125 实测,fixture 测试锁住假设;漂移时 fixture 先红):
8
10
  // - {type:'thread.started' | 'turn.started'} 划 turn 边界
9
11
  // - {type:'turn.completed', usage} / {type:'turn.failed', error} 收尾
10
- // - {type:'item.started', item:{type, status:'in_progress', ...}} 是 in_progress 占位 → 跳过
12
+ // - {type:'item.started', item:{type, status:'in_progress', ...}} 暂存;缺 completed 时保留为 unknown
11
13
  // - {type:'item.completed', item:{type:'agent_message', text:'...'}} → assistant 文本
12
14
  // - {type:'item.completed', item:{type:'command_execution', command, aggregated_output, exit_code, status}} → ToolCallInfo
13
- // - 其他 item.type(file_read/file_write/web_search 等)best-effort 抽 input/output
14
- // codex 无 sub-agent 概念,numSubAgents 恒 0。字段缺失静默 skip 不 throw。
15
+ // - 其他 item.type(file_read/file_write/web_search/agent tools 等)best-effort 抽 input/output
16
+ // 子代理统计只认事件流里的具体 spawn 工具事实;旧协议没有这类事件时自然为 0。
15
17
  const TOOL_INPUT_LIMIT = 500;
16
18
  const TOOL_OUTPUT_LIMIT = 500;
17
19
  export function isCodexResultEvent(event) {
18
20
  return event.type === 'turn.completed' || event.type === 'turn.failed';
19
21
  }
20
22
  function extractToolInput(item, itemType) {
21
- if (itemType === 'command_execution')
22
- return (item.command || '').slice(0, TOOL_INPUT_LIMIT);
23
- if (itemType === 'file_read' || itemType === 'file_write')
24
- return item.path || null;
23
+ if (itemType === 'command_execution') {
24
+ return { command: safeSliceForJson(item.command || '', TOOL_INPUT_LIMIT, '') };
25
+ }
26
+ if (itemType === 'file_read' || itemType === 'file_write') {
27
+ return { file_path: item.path || null };
28
+ }
29
+ if (itemType === 'file_change') {
30
+ return item.changes?.slice(0, 50).map((change) => ({
31
+ path: safeSliceForJson(change.path || '', TOOL_INPUT_LIMIT, ''),
32
+ ...(change.changeKind && { kind: change.changeKind }),
33
+ })) ?? [];
34
+ }
25
35
  if (itemType === 'web_search')
26
- return item.query || null;
36
+ return { query: item.query || null };
37
+ if (itemType === 'mcp_tool_call')
38
+ return item.arguments ?? null;
27
39
  return null;
28
40
  }
41
+ function stringifyToolValue(value) {
42
+ try {
43
+ const json = JSON.stringify(value);
44
+ return safeSliceForJson(json ?? String(value), TOOL_OUTPUT_LIMIT, '');
45
+ }
46
+ catch {
47
+ return safeSliceForJson(String(value), TOOL_OUTPUT_LIMIT, '');
48
+ }
49
+ }
29
50
  function extractToolOutput(item, itemType) {
30
51
  if (itemType === 'command_execution' && item.aggregated_output) {
31
- return item.aggregated_output.slice(0, TOOL_OUTPUT_LIMIT);
52
+ return safeSliceForJson(item.aggregated_output, TOOL_OUTPUT_LIMIT, '');
32
53
  }
33
54
  if (itemType === 'file_read' && item.content)
34
- return item.content.slice(0, TOOL_OUTPUT_LIMIT);
55
+ return safeSliceForJson(item.content, TOOL_OUTPUT_LIMIT, '');
56
+ if (itemType === 'file_change')
57
+ return item.status || '';
58
+ if (itemType === 'mcp_tool_call') {
59
+ if (item.error?.message)
60
+ return safeSliceForJson(item.error.message, TOOL_OUTPUT_LIMIT, '');
61
+ if (item.result !== undefined)
62
+ return stringifyToolValue(item.result);
63
+ }
64
+ if (itemType === 'error' && item.message)
65
+ return safeSliceForJson(item.message, TOOL_OUTPUT_LIMIT, '');
35
66
  if (item.results)
36
- return JSON.stringify(item.results).slice(0, TOOL_OUTPUT_LIMIT);
67
+ return stringifyToolValue(item.results);
37
68
  return '';
38
69
  }
70
+ function codexItemStatus(event, item) {
71
+ if (event.error
72
+ || item.error
73
+ || item.type === 'error'
74
+ || (typeof item.exit_code === 'number' && item.exit_code !== 0))
75
+ return 'failure';
76
+ const status = item.status?.toLowerCase();
77
+ if (status === 'failed' || status === 'error')
78
+ return 'failure';
79
+ if (status === 'cancelled' || status === 'canceled')
80
+ return 'cancelled';
81
+ if (status === 'completed'
82
+ || status === 'complete'
83
+ || status === 'success'
84
+ || status === 'succeeded')
85
+ return 'success';
86
+ if (status)
87
+ return 'unknown';
88
+ if (typeof item.exit_code === 'number' && item.exit_code === 0)
89
+ return 'success';
90
+ // `item.completed` is the legacy success signal when older Codex events omit
91
+ // item.status. Keep that compatibility while preferring explicit status above.
92
+ return 'success';
93
+ }
39
94
  export function extractCodexTrace(events) {
40
95
  const turns = [];
41
96
  const toolCalls = [];
42
97
  let fullNumTurns = 0;
43
- // codex 没有 Agent 工具(像 Claude 的 sub-agent),恒 0
44
- const numSubAgents = 0;
98
+ let numSubAgents = 0;
45
99
  let currentTurnText = '';
46
100
  let currentTurnTools = [];
47
101
  let currentTurnHasContent = false;
48
102
  let lastTurnTs;
103
+ const pendingItems = new Map();
104
+ const appendToolCall = (item, status, statusSource, callInstanceId) => {
105
+ const itemType = item.type;
106
+ if (!itemType || isNonToolItem(itemType))
107
+ return;
108
+ if (isAgentSpawnItem(item))
109
+ numSubAgents += 1;
110
+ const toolIdentity = normalizeToolIdentity({
111
+ sourceName: itemType,
112
+ provider: itemType === 'mcp_tool_call' ? item.server : undefined,
113
+ authoritativeName: itemType === 'mcp_tool_call' ? item.tool ?? item.name : undefined,
114
+ });
115
+ const tc = {
116
+ tool: toolIdentity.name,
117
+ ...(toolIdentity.sourceName ? { sourceTool: toolIdentity.sourceName } : {}),
118
+ ...(toolIdentity.namespace ? { toolNamespace: toolIdentity.namespace } : {}),
119
+ ...(toolIdentity.provider ? { toolProvider: toolIdentity.provider } : {}),
120
+ input: extractToolInput(item, itemType),
121
+ output: extractToolOutput(item, itemType),
122
+ status,
123
+ statusSource,
124
+ success: status === 'success',
125
+ callInstanceId,
126
+ toolUseId: item.id,
127
+ };
128
+ currentTurnTools.push(tc);
129
+ toolCalls.push(tc);
130
+ currentTurnHasContent = true;
131
+ };
132
+ const materializePendingItems = () => {
133
+ for (const queue of pendingItems.values()) {
134
+ for (const pending of queue) {
135
+ appendToolCall(pending.item, 'unknown', 'unknown', pending.callInstanceId);
136
+ }
137
+ }
138
+ pendingItems.clear();
139
+ };
49
140
  const flushTurn = (closingEvent) => {
50
141
  if (!currentTurnHasContent && currentTurnTools.length === 0)
51
142
  return;
@@ -65,11 +156,13 @@ export function extractCodexTrace(events) {
65
156
  if (closingEvent?.ts)
66
157
  lastTurnTs = closingEvent.ts;
67
158
  };
68
- for (const event of events) {
159
+ for (let eventIndex = 0; eventIndex < events.length; eventIndex += 1) {
160
+ const event = events[eventIndex];
69
161
  const t = event.type;
70
162
  if (!t)
71
163
  continue;
72
164
  if (t === 'thread.started' || t === 'turn.started') {
165
+ materializePendingItems();
73
166
  if (currentTurnHasContent || currentTurnTools.length > 0)
74
167
  flushTurn();
75
168
  if (event.ts)
@@ -77,25 +170,59 @@ export function extractCodexTrace(events) {
77
170
  continue;
78
171
  }
79
172
  if (t === 'turn.completed') {
173
+ materializePendingItems();
80
174
  flushTurn(event);
81
175
  continue;
82
176
  }
83
177
  if (t === 'turn.failed') {
84
- if (currentTurnTools.length > 0) {
85
- currentTurnTools[currentTurnTools.length - 1].success = false;
86
- }
178
+ materializePendingItems();
87
179
  flushTurn(event);
88
180
  continue;
89
181
  }
90
- // 只关心 item.completed —— item.started 是 in_progress 占位,后面会被 completed 覆盖
182
+ if (t === 'item.started') {
183
+ const item = event.item;
184
+ if (item?.id && item.type && item.type !== 'agent_message') {
185
+ const pending = pendingItems.get(item.id) ?? [];
186
+ pending.push({
187
+ item,
188
+ callInstanceId: `codex-exec:${eventIndex}:started`,
189
+ });
190
+ pendingItems.set(item.id, pending);
191
+ }
192
+ continue;
193
+ }
194
+ if (t === 'item.updated') {
195
+ const item = event.item;
196
+ if (item?.id && item.type && item.type !== 'agent_message') {
197
+ const pending = pendingItems.get(item.id);
198
+ const current = pending?.at(-1);
199
+ if (current) {
200
+ current.item = { ...current.item, ...item };
201
+ }
202
+ else {
203
+ pendingItems.set(item.id, [{
204
+ item,
205
+ callInstanceId: `codex-exec:${eventIndex}:updated`,
206
+ }]);
207
+ }
208
+ }
209
+ continue;
210
+ }
91
211
  if (t !== 'item.completed')
92
212
  continue;
93
213
  const item = event.item;
94
214
  if (!item || !item.type)
95
215
  continue;
96
- const itemType = item.type;
216
+ const pendingQueue = item.id ? pendingItems.get(item.id) : undefined;
217
+ const started = pendingQueue?.shift();
218
+ if (item.id && pendingQueue?.length === 0)
219
+ pendingItems.delete(item.id);
220
+ const completedItem = started
221
+ ? { ...started.item, ...item, status: item.status }
222
+ : item;
223
+ const itemType = completedItem.type;
97
224
  if (itemType === 'agent_message') {
98
- const txt = item.text || '';
225
+ const txt = completedItem.text || '';
99
226
  if (txt) {
100
227
  if (currentTurnText)
101
228
  currentTurnText += '\n';
@@ -104,19 +231,27 @@ export function extractCodexTrace(events) {
104
231
  }
105
232
  continue;
106
233
  }
107
- // 其他 item type 当 tool call
108
- const isError = !!event.error || (typeof item.exit_code === 'number' && item.exit_code !== 0);
109
- const tc = {
110
- tool: itemType,
111
- input: extractToolInput(item, itemType),
112
- output: extractToolOutput(item, itemType),
113
- success: !isError,
114
- };
115
- currentTurnTools.push(tc);
116
- toolCalls.push(tc);
117
- currentTurnHasContent = true;
234
+ const status = codexItemStatus(event, completedItem);
235
+ appendToolCall(completedItem, status, 'runtime', started?.callInstanceId ?? `codex-exec:${eventIndex}:completed`);
118
236
  }
119
237
  // 流末尾 flush(防止最后一个 turn 没 turn.completed 收尾)
238
+ materializePendingItems();
120
239
  flushTurn();
121
240
  return { turns, toolCalls, fullNumTurns, numSubAgents };
122
241
  }
242
+ function isAgentSpawnItem(item) {
243
+ const identity = [
244
+ item.type,
245
+ item.server,
246
+ item.tool,
247
+ item.name,
248
+ ].filter((value) => typeof value === 'string').join(' ').toLowerCase();
249
+ return /(?:^|[^a-z0-9])(?:spawn|create|start)[_-]?(?:sub)?agent(?:$|[^a-z0-9])/.test(identity)
250
+ || /^(?:agent|subagent|task)$/.test(item.type?.toLowerCase() ?? '');
251
+ }
252
+ function isNonToolItem(itemType) {
253
+ return itemType === 'agent_message'
254
+ || itemType === 'reasoning'
255
+ || itemType === 'todo_list'
256
+ || itemType === 'error';
257
+ }
@@ -1,14 +1,12 @@
1
1
  import type { ExecResult, ExecutorInput } from '../types/index.js';
2
2
  import type { CodexEvent } from './shared.js';
3
+ export { extractCodexFinalOutput, extractCodexProtocolError, extractCodexStopReason, extractCodexUsage, sumCodexElapsed, } from './codex-protocol.js';
3
4
  export declare function isolateCodexCwd(allowedSkills: string[] | undefined, cwd: string | null | undefined, executorName?: string): void;
4
- export declare function extractCodexUsage(events: CodexEvent[]): {
5
- input: number;
6
- cached: number;
7
- output: number;
8
- };
9
- export declare function extractCodexFinalOutput(events: CodexEvent[]): string;
10
- export declare function extractCodexStopReason(events: CodexEvent[]): string;
11
- export declare function sumCodexElapsed(resultEvents: CodexEvent[], wallClock: number): number;
5
+ export interface CodexJsonlParseResult {
6
+ events: CodexEvent[];
7
+ malformedLineCount: number;
8
+ }
9
+ export declare function parseCodexJsonl(stdout: string): CodexJsonlParseResult;
12
10
  export declare function buildCodexArgs({ model, cwd, prompt }: {
13
11
  model: string;
14
12
  cwd?: string | null;
@@ -1,12 +1,13 @@
1
- import { extractCodexTrace, isCodexResultEvent } from './codex-cli-trace.js';
2
- import { buildExecEnv, DEFAULT_TIMEOUT_MS, errorMessage, interruptedExecResult, MAX_BUFFER, spawnWithSigintPropagation, timeoutExecResult, } from './shared.js';
1
+ import { buildCodexResult } from './codex-protocol.js';
2
+ import { buildExecEnv, DEFAULT_TIMEOUT_MS, errorMessage, interruptedExecResult, MAX_BUFFER, normalizeCodexProtocolEvent, spawnWithSigintPropagation, timeoutExecResult, } from './shared.js';
3
+ export { extractCodexFinalOutput, extractCodexProtocolError, extractCodexStopReason, extractCodexUsage, sumCodexElapsed, } from './codex-protocol.js';
3
4
  // codex CLI(0.125)隔离能力对比 claude-cli:
4
5
  // Claude:三条 channel(SDK skills auto-discovery / subagent Skill 工具 /
5
6
  // cwd 文件系统),`--disable-slash-commands` + `--disallowedTools Skill`
6
7
  // 堵前两条,cwd 切空目录堵第三条
7
- // Codex:无 SDK 等价 channel(它就是 CLI 不是 SDK),无 subagent Skill 等价
8
- // (没有 Agent 工具),只剩 cwd 文件系统这一条 channel(`-C/--cd`)
9
- // → AGENTS.md / .agents/skills/ 自动加载只能靠 cwd 切到隔离空目录避免
8
+ // Codex:CLI / SDK 的项目指令、skills 与可能的子线程都以 cwd 工作区为
9
+ // 发现边界;`-C/--cd` 是可验证的隔离控制面。因此把 cwd 切到
10
+ // 隔离空目录,统一阻断 AGENTS.md / .agents/skills/ 自动加载。
10
11
  //
11
12
  // undefined → 不传 -C(原行为,看 cwd 里有什么 codex 自己决定)
12
13
  // [] → 必须提供 cwd 非空(否则 throw),caller 应传一个
@@ -19,11 +20,10 @@ export function isolateCodexCwd(allowedSkills, cwd, executorName = 'codex-cli')
19
20
  throw new Error(`skill 白名单(allowedSkills=${JSON.stringify(allowedSkills)})不再支持:非空白名单无法真正隔离。\n`
20
21
  + ` 仅支持 [](强制 cwd 隔离,需提供 cwd 非空,全封死)或 undefined(不隔离)。`);
21
22
  }
22
- // allowedSkills === [] 时必须有 cwd(channel 3 cwd 隔离是 codex 唯一 channel)
23
+ // allowedSkills === [] 时必须有 cwd;工作区隔离是 Codex 的控制面。
23
24
  if (!cwd) {
24
- throw new Error(`${executorName} executor allowedSkills=[] 需要提供 cwd 非空(channel 3 cwd 隔离)。\n`
25
- + ' codex 没有 SDK skill 自动发现 / subagent Skill 工具这两条 channel,\n'
26
- + ' cwd 文件系统隔离是它唯一能堵 AGENTS.md / .agents/skills/ 自动加载的途径。\n'
25
+ throw new Error(`${executorName} executor allowedSkills=[] 需要提供 cwd 非空(工作区隔离)。\n`
26
+ + ' 主线程与可能的子线程都会继承工作区上下文;必须用隔离 cwd 阻断 AGENTS.md / .agents/skills/ 自动加载。\n'
27
27
  + ' caller 应传一个 isolated 空目录(如 ~/.oh-my-knowledge/state/isolated-cwd/)。');
28
28
  }
29
29
  }
@@ -32,67 +32,37 @@ export function isolateCodexCwd(allowedSkills, cwd, executorName = 'codex-cli')
32
32
  // 每次都打两行会让 stderr 被同样文本刷屏(140-280 行)。模块级 flag 一个 process 只打一次。
33
33
  let hasWarnedSystem = false;
34
34
  let hasWarnedCost = false;
35
- function parseCodexJsonl(stdout) {
35
+ export function parseCodexJsonl(stdout) {
36
36
  const events = [];
37
+ let malformedLineCount = 0;
37
38
  for (const line of stdout.split('\n')) {
38
39
  const trimmed = line.trim();
39
40
  if (!trimmed)
40
41
  continue;
41
42
  try {
42
- events.push(JSON.parse(trimmed));
43
+ const value = JSON.parse(trimmed);
44
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
45
+ malformedLineCount += 1;
46
+ continue;
47
+ }
48
+ const event = normalizeCodexProtocolEvent(value);
49
+ if (!event
50
+ || typeof event.type !== 'string'
51
+ || event.type.trim() === ''
52
+ || (event.type.startsWith('item.')
53
+ && (!event.item
54
+ || typeof event.item.type !== 'string'
55
+ || event.item.type.trim() === ''))) {
56
+ malformedLineCount += 1;
57
+ continue;
58
+ }
59
+ events.push(event);
60
+ }
61
+ catch {
62
+ malformedLineCount += 1;
43
63
  }
44
- catch { /* skip non-JSON lines */ }
45
- }
46
- return events;
47
- }
48
- export function extractCodexUsage(events) {
49
- let input = 0;
50
- let cached = 0;
51
- let output = 0;
52
- for (const e of events) {
53
- if (!isCodexResultEvent(e) || !e.usage)
54
- continue;
55
- input += e.usage.input_tokens || 0;
56
- cached += e.usage.cached_input_tokens || 0;
57
- output += e.usage.output_tokens || 0;
58
- }
59
- return { input, cached, output };
60
- }
61
- // exported for unit tests
62
- export function extractCodexFinalOutput(events) {
63
- // 抓 item.completed + item.type='agent_message' 的 text 全部拼起来。
64
- // codex 同 turn 内可能 emit 多条 agent_message(如 "step 1" / "step 2" / "final answer"),
65
- // 取最后一条会让 grader / assertion / LLM judge 只看到最后片段,跟 extractCodexTrace
66
- // 把多条拼到 turn.content 的语义不一致(test/executors/codex-cli-trace.test.ts:120-130 锁住)。
67
- // 一致化:final output 也拼,跟 trace UI 看到的内容对齐。
68
- const allTexts = [];
69
- for (const e of events) {
70
- if (e.type !== 'item.completed')
71
- continue;
72
- if (e.item?.type !== 'agent_message')
73
- continue;
74
- const txt = e.item.text;
75
- if (txt)
76
- allTexts.push(txt);
77
64
  }
78
- return allTexts.join('\n');
79
- }
80
- export function extractCodexStopReason(events) {
81
- const last = [...events].reverse().find((e) => isCodexResultEvent(e));
82
- if (!last)
83
- return 'unknown';
84
- if (last.type === 'turn.failed')
85
- return 'error';
86
- return last.stop_reason || 'end_turn';
87
- }
88
- // codex 每个 turn.completed 携带本 turn 的 elapsed_ms(per-turn delta),
89
- // 跟 extractCodexUsage 把 token 累加多 turn 是同一语义假设(events 是 per-turn delta,
90
- // 不是 cumulative)。multi-turn 只取最后一条会漏算前面 turn 的耗时。fallback:
91
- // 累加为 0 / 全部缺失时退到 wall-clock,避免 elapsed_ms===0 单 turn 抹平 duration。
92
- // exported for unit tests
93
- export function sumCodexElapsed(resultEvents, wallClock) {
94
- const total = resultEvents.reduce((s, e) => s + (e.elapsed_ms ?? 0), 0);
95
- return total > 0 ? total : wallClock;
65
+ return { events, malformedLineCount };
96
66
  }
97
67
  // exported for arg-shape regression tests
98
68
  export function buildCodexArgs({ model, cwd, prompt }) {
@@ -104,6 +74,7 @@ export function buildCodexArgs({ model, cwd, prompt }) {
104
74
  '--json',
105
75
  '--ephemeral', // 不持久化 session 文件
106
76
  '--ignore-user-config', // 不读 $CODEX_HOME/config.toml
77
+ '--ignore-rules', // 不让用户 / 项目 execpolicy 改写工具行为
107
78
  '--skip-git-repo-check', // 允许 isolated cwd 不是 git 仓库
108
79
  '--sandbox', 'read-only', // 评测场景不需要写文件
109
80
  '-c', 'approval_policy="never"', // non-interactive 必须;TOML 字符串需要 quote
@@ -155,53 +126,13 @@ export async function codexCliExecutor({ model, system, prompt, cwd, skillDir, t
155
126
  ...(cwd && { cwd }),
156
127
  });
157
128
  const durationMs = Date.now() - start;
158
- const events = parseCodexJsonl(stdout);
159
- const resultEvents = events.filter(isCodexResultEvent);
160
- if (resultEvents.length === 0) {
161
- return {
162
- ok: false,
163
- error: 'no turn.completed/turn.failed event in codex --json output',
164
- durationMs,
165
- durationApiMs: 0,
166
- inputTokens: 0,
167
- outputTokens: 0,
168
- cacheReadTokens: 0,
169
- cacheCreationTokens: 0,
170
- costUSD: 0,
171
- costReportedByExecutor: false,
172
- output: null,
173
- stopReason: 'error',
174
- numTurns: 0,
175
- };
176
- }
177
- const last = resultEvents[resultEvents.length - 1];
178
- const usage = extractCodexUsage(events);
179
- const trace = extractCodexTrace(events);
180
- const stopReason = extractCodexStopReason(events);
181
- const finalOutput = extractCodexFinalOutput(events);
182
- const ok = last.type !== 'turn.failed' && !last.error;
183
- return {
184
- ok,
185
- // multi-turn 累加 elapsed_ms(per-turn delta);全 0 fallback wall-clock。详见 sumCodexElapsed
186
- durationMs: sumCodexElapsed(resultEvents, durationMs),
187
- durationApiMs: 0, // codex 不报 API duration
188
- inputTokens: usage.input,
189
- outputTokens: usage.output,
190
- cacheReadTokens: usage.cached,
191
- cacheCreationTokens: 0, // codex 不报 cache creation
192
- costUSD: 0, // 占位:codex 0.125 binary 不输出 cost
193
- costReportedByExecutor: false, // renderer 据此显示「未报告」而非 $0.0000
194
- output: finalOutput,
195
- stopReason,
196
- // success path:ok=false(turn.failed)时透传 last.error.message 给 caller 看到
197
- // 失败原因。catch path 历史上已经透传(对称化)。
198
- ...(!ok && { error: last.error?.message || 'codex turn.failed' }),
199
- numTurns: resultEvents.length,
200
- fullNumTurns: trace.fullNumTurns,
201
- numSubAgents: trace.numSubAgents,
202
- ...(trace.turns.length > 0 && { turns: trace.turns }),
203
- ...(trace.toolCalls.length > 0 && { toolCalls: trace.toolCalls }),
204
- };
129
+ const parsed = parseCodexJsonl(stdout);
130
+ return buildCodexResult({
131
+ events: parsed.events,
132
+ wallClockDurationMs: durationMs,
133
+ source: 'codex --json',
134
+ malformedLineCount: parsed.malformedLineCount,
135
+ });
205
136
  }
206
137
  catch (err) {
207
138
  const durationMs = Date.now() - start;
@@ -210,47 +141,14 @@ export async function codexCliExecutor({ model, system, prompt, cwd, skillDir, t
210
141
  return { ...timeoutExecResult(timeoutMs, durationMs), costReportedByExecutor: false };
211
142
  if (details.killedBySignal)
212
143
  return { ...interruptedExecResult(durationMs), costReportedByExecutor: false };
213
- // 尝试从 stdout parse 部分结果(同 claude-cli 防御性写法)
214
- const events = parseCodexJsonl(details.stdout || '');
215
- const resultEvents = events.filter(isCodexResultEvent);
216
- if (resultEvents.length > 0) {
217
- const last = resultEvents[resultEvents.length - 1];
218
- const usage = extractCodexUsage(events);
219
- const trace = extractCodexTrace(events);
220
- return {
221
- ok: false,
222
- error: last.error?.message || errorMessage(err),
223
- durationMs: sumCodexElapsed(resultEvents, durationMs),
224
- durationApiMs: 0,
225
- inputTokens: usage.input,
226
- outputTokens: usage.output,
227
- cacheReadTokens: usage.cached,
228
- cacheCreationTokens: 0,
229
- costUSD: 0,
230
- costReportedByExecutor: false,
231
- output: extractCodexFinalOutput(events) || null,
232
- stopReason: 'error',
233
- numTurns: resultEvents.length,
234
- fullNumTurns: trace.fullNumTurns,
235
- numSubAgents: trace.numSubAgents,
236
- ...(trace.turns.length > 0 && { turns: trace.turns }),
237
- ...(trace.toolCalls.length > 0 && { toolCalls: trace.toolCalls }),
238
- };
239
- }
240
- return {
241
- ok: false,
242
- error: errorMessage(err),
243
- durationMs,
244
- durationApiMs: 0,
245
- inputTokens: 0,
246
- outputTokens: 0,
247
- cacheReadTokens: 0,
248
- cacheCreationTokens: 0,
249
- costUSD: 0,
250
- costReportedByExecutor: false,
251
- output: null,
252
- stopReason: 'error',
253
- numTurns: 0,
254
- };
144
+ // 失败路径仍保留已完成的事件、用量与 trace,但不能反转为成功。
145
+ const parsed = parseCodexJsonl(details.stdout || '');
146
+ return buildCodexResult({
147
+ events: parsed.events,
148
+ wallClockDurationMs: durationMs,
149
+ source: 'codex --json',
150
+ malformedLineCount: parsed.malformedLineCount,
151
+ forcedError: details.stderr?.trim() || errorMessage(err),
152
+ });
255
153
  }
256
154
  }
@@ -0,0 +1,24 @@
1
+ import type { ExecResult } from '../types/index.js';
2
+ import type { CodexEvent } from './shared.js';
3
+ export declare function extractCodexUsage(events: CodexEvent[]): {
4
+ input: number;
5
+ cached: number;
6
+ output: number;
7
+ };
8
+ /**
9
+ * Match @openai/codex-sdk's `finalResponse`: the latest completed
10
+ * agent_message is the answer. Earlier messages remain available in `turns`.
11
+ */
12
+ export declare function extractCodexFinalOutput(events: CodexEvent[]): string;
13
+ export declare function extractCodexProtocolError(events: CodexEvent[]): string | undefined;
14
+ export declare function validateCodexProtocol(events: CodexEvent[]): string | undefined;
15
+ export declare function extractCodexStopReason(events: CodexEvent[]): string;
16
+ export declare function sumCodexElapsed(resultEvents: CodexEvent[], wallClock: number): number;
17
+ export interface BuildCodexResultOptions {
18
+ events: CodexEvent[];
19
+ wallClockDurationMs: number;
20
+ source: 'codex --json' | 'codex-sdk';
21
+ malformedLineCount?: number;
22
+ forcedError?: string;
23
+ }
24
+ export declare function buildCodexResult({ events, wallClockDurationMs, source, malformedLineCount, forcedError, }: BuildCodexResultOptions): ExecResult;