lark-remote 0.5.19 → 0.5.21
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/CHANGELOG.md +29 -0
- package/dist/bridge/approval-actions.d.ts +1 -1
- package/dist/bridge/approval-actions.js +1 -1
- package/dist/bridge/bash-run.d.ts +71 -0
- package/dist/bridge/bash-run.js +217 -0
- package/dist/bridge/compact.d.ts +155 -0
- package/dist/bridge/compact.js +450 -0
- package/dist/bridge/final-usage.js +4 -4
- package/dist/bridge/inbound-media.js +72 -46
- package/dist/bridge/index.d.ts +106 -153
- package/dist/bridge/index.js +400 -1000
- package/dist/bridge/job-watch.d.ts +68 -0
- package/dist/bridge/job-watch.js +146 -0
- package/dist/bridge/queue-manager.d.ts +3 -3
- package/dist/bridge/queue-manager.js +10 -29
- package/dist/card/approval-render.d.ts +2 -2
- package/dist/card/approval-render.js +141 -130
- package/dist/card/card-budget.js +123 -98
- package/dist/card/card-helpers.d.ts +3 -3
- package/dist/card/card-helpers.js +6 -6
- package/dist/card/final-answer-file.js +1 -1
- package/dist/card/run-renderer.d.ts +3 -3
- package/dist/card/run-renderer.js +114 -159
- package/dist/card/run-state.d.ts +1 -15
- package/dist/card/run-state.js +358 -292
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +21 -17
- package/dist/card/usage-format.d.ts +22 -7
- package/dist/card/usage-format.js +119 -100
- package/dist/cli.js +15 -3
- package/dist/clone.js +2 -2
- package/dist/common/text-width.js +36 -24
- package/dist/common/tool-summary.js +132 -90
- package/dist/config/codex-config.js +130 -106
- package/dist/config/index.d.ts +16 -2
- package/dist/config/index.js +23 -8
- package/dist/connector/index.js +6 -6
- package/dist/error-classification.js +3 -2
- package/dist/inbound/placeholder.d.ts +1 -1
- package/dist/inbound/placeholder.js +1 -1
- package/dist/index.js +387 -281
- package/dist/logger/index.js +6 -6
- package/dist/message-handlers.d.ts +0 -5
- package/dist/message-handlers.js +254 -237
- package/dist/model-catalog.d.ts +2 -2
- package/dist/model-catalog.js +3 -8
- package/dist/platform/identity.js +1 -1
- package/dist/platform/path.d.ts +1 -1
- package/dist/platform/path.js +1 -1
- package/dist/platform/sleep-blocker.js +1 -1
- package/dist/platform/terminator-win32.js +2 -1
- package/dist/router/commands/ls.d.ts +3 -0
- package/dist/router/commands/ls.js +256 -175
- package/dist/router/commands/order.js +184 -159
- package/dist/router/commands/resume.js +190 -152
- package/dist/router/commands/ws.js +219 -174
- package/dist/router/config/dsh.d.ts +4 -8
- package/dist/router/config/dsh.js +3 -10
- package/dist/router/config/kimi.js +1 -1
- package/dist/router/config/types.d.ts +1 -1
- package/dist/router/config-diff.js +1 -1
- package/dist/router/index.d.ts +56 -11
- package/dist/router/index.js +401 -391
- package/dist/runner/bash/runner.js +2 -4
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +6 -4
- package/dist/runner/codex/app-server/runner.d.ts +9 -2
- package/dist/runner/codex/app-server/runner.js +30 -25
- package/dist/runner/codex/app-server/translator.d.ts +2 -5
- package/dist/runner/codex/app-server/translator.js +291 -238
- package/dist/runner/common/acp/acp-approval.d.ts +1 -1
- package/dist/runner/common/acp/acp-approval.js +1 -1
- package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
- package/dist/runner/common/acp/base-acp-runner.js +26 -65
- package/dist/runner/common/acp/base-translator.d.ts +12 -2
- package/dist/runner/common/acp/base-translator.js +50 -26
- package/dist/runner/common/acp/protocol-types.d.ts +1 -1
- package/dist/runner/common/acp/protocol-types.js +2 -2
- package/dist/runner/common/connection-based-runner.d.ts +32 -1
- package/dist/runner/common/connection-based-runner.js +32 -2
- package/dist/runner/common/jsonl-stream.js +154 -187
- package/dist/runner/common/jsonrpc/client.d.ts +29 -0
- package/dist/runner/common/jsonrpc/client.js +43 -12
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
- package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
- package/dist/runner/common/spawning-runner.d.ts +6 -1
- package/dist/runner/common/spawning-runner.js +9 -4
- package/dist/runner/common/turn-started.d.ts +8 -0
- package/dist/runner/common/turn-started.js +11 -0
- package/dist/runner/dsh/approval.js +84 -51
- package/dist/runner/dsh/catalog-probe.d.ts +17 -0
- package/dist/runner/dsh/catalog-probe.js +10 -0
- package/dist/runner/dsh/client.d.ts +7 -11
- package/dist/runner/dsh/client.js +356 -289
- package/dist/runner/dsh/exit-cancel.d.ts +2 -2
- package/dist/runner/dsh/exit-cancel.js +2 -2
- package/dist/runner/dsh/inflight.d.ts +1 -1
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/mux-stream.js +28 -8
- package/dist/runner/dsh/runner.d.ts +44 -4
- package/dist/runner/dsh/runner.js +296 -199
- package/dist/runner/dsh/translator.d.ts +1 -31
- package/dist/runner/dsh/translator.js +180 -178
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -2
- package/dist/runner/kimi/acp/runner.js +82 -46
- package/dist/runner/kimi/acp/translator.d.ts +1 -1
- package/dist/runner/kimi/acp/translator.js +3 -4
- package/dist/runner/opencode/acp/error-monitor.js +5 -5
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/pi/rpc/client.js +4 -13
- package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
- package/dist/runner/pi/rpc/protocol-types.js +1 -1
- package/dist/runner/pi/rpc/runner.d.ts +3 -0
- package/dist/runner/pi/rpc/runner.js +27 -42
- package/dist/runner/pi/rpc/translator.js +61 -60
- package/dist/runner/types.d.ts +2 -2
- package/dist/runner/types.js +1 -1
- package/dist/session/claude/sessions.d.ts +45 -0
- package/dist/session/claude/sessions.js +160 -122
- package/dist/session/codex/rollout-reader.js +181 -182
- package/dist/session/common/throw-session-list-error.d.ts +10 -0
- package/dist/session/common/throw-session-list-error.js +16 -0
- package/dist/session/dsh/sessions.js +291 -235
- package/dist/session/kimi/sessions.d.ts +40 -8
- package/dist/session/kimi/sessions.js +373 -301
- package/dist/session/opencode/sessions.d.ts +142 -1
- package/dist/session/opencode/sessions.js +378 -321
- package/dist/session/pi/sessions.js +4 -5
- package/dist/session/session-store.d.ts +9 -1
- package/dist/session/session-store.js +99 -99
- package/dist/update/version-check.js +2 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +1 -1
|
@@ -117,67 +117,189 @@ function readCodexRolloutSummary(filePath) {
|
|
|
117
117
|
return null;
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
|
+
function newScanState() {
|
|
121
|
+
return {
|
|
122
|
+
sessionMeta: null,
|
|
123
|
+
events: [],
|
|
124
|
+
realUserMessages: [],
|
|
125
|
+
lastRawUsage: undefined,
|
|
126
|
+
lastContextLimit: undefined,
|
|
127
|
+
lastTotalUsage: undefined,
|
|
128
|
+
previousTotals: undefined,
|
|
129
|
+
turnUsage: null,
|
|
130
|
+
compactCount: 0,
|
|
131
|
+
compactPreContextLength: undefined,
|
|
132
|
+
compactPostContextLength: undefined,
|
|
133
|
+
lastUserMessageSource: null,
|
|
134
|
+
userResponseItemCount: 0,
|
|
135
|
+
previousRawUsage: undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function scanResult(state) {
|
|
139
|
+
return {
|
|
140
|
+
sessionMeta: state.sessionMeta,
|
|
141
|
+
events: state.events,
|
|
142
|
+
realUserMessages: state.realUserMessages,
|
|
143
|
+
lastRawUsage: state.lastRawUsage,
|
|
144
|
+
lastContextLimit: state.lastContextLimit,
|
|
145
|
+
lastTotalUsage: state.lastTotalUsage,
|
|
146
|
+
previousTotals: state.previousTotals,
|
|
147
|
+
turnUsage: state.turnUsage,
|
|
148
|
+
compactCount: state.compactCount,
|
|
149
|
+
compactPreContextLength: state.compactPreContextLength,
|
|
150
|
+
compactPostContextLength: state.compactPostContextLength,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function warnIfVocabularyDrift(state, filePath) {
|
|
154
|
+
// 兼容性漂移探针:文件里有 `role:"user"` 注入,却识别不出任何真人输入 ——
|
|
155
|
+
// 说明 codex 又换了 event_msg 词表,displayTitle 会静默变空(0.153 事故形态)。
|
|
156
|
+
// 只告警不兜底:误用注入文本当标题是已修掉的缺陷。
|
|
157
|
+
if (state.realUserMessages.length === 0 && state.userResponseItemCount > 0) {
|
|
158
|
+
getLogger().warn(`[codex-rollout-reader] no human user message recognised in ${filePath} ` +
|
|
159
|
+
`(${state.userResponseItemCount} role:"user" response_items seen) — codex rollout ` +
|
|
160
|
+
`event vocabulary may have changed; session title will be empty`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function recordHumanMessage(state, human) {
|
|
164
|
+
const text = human.text.slice(0, 200);
|
|
165
|
+
const prev = state.realUserMessages[state.realUserMessages.length - 1];
|
|
166
|
+
// 过渡期防御:同一 turn 若被旧版 user_message 与新版 item_completed
|
|
167
|
+
// 各写一次,会得到相邻重复。仅在「通道切换且文本与上一条完全相同」
|
|
168
|
+
// 时跳过——同通道连续相同输入(真人连发两条一样的话)不受影响。
|
|
169
|
+
const duplicateAcrossChannels = state.lastUserMessageSource !== null &&
|
|
170
|
+
state.lastUserMessageSource !== human.source &&
|
|
171
|
+
prev === text;
|
|
172
|
+
if (!duplicateAcrossChannels) {
|
|
173
|
+
state.realUserMessages.push(text);
|
|
174
|
+
}
|
|
175
|
+
state.lastUserMessageSource = human.source;
|
|
176
|
+
}
|
|
177
|
+
function resolveRawUsage(info, previousTotals) {
|
|
178
|
+
const last = info?.last_token_usage;
|
|
179
|
+
const total = info?.total_token_usage;
|
|
180
|
+
let raw;
|
|
181
|
+
if (last) {
|
|
182
|
+
raw = last;
|
|
183
|
+
}
|
|
184
|
+
else if (total && previousTotals) {
|
|
185
|
+
raw = subtractRawUsage(total, previousTotals);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
raw = total;
|
|
189
|
+
}
|
|
190
|
+
return { raw, total };
|
|
191
|
+
}
|
|
192
|
+
function accumulateTurnUsage(state, raw) {
|
|
193
|
+
if (!state.turnUsage)
|
|
194
|
+
return;
|
|
195
|
+
// token_count 每次采样一条,末 turn 的所有采样求和(见 buildUsageFromScan)。
|
|
196
|
+
const cached = Math.min(raw.cached_input_tokens, raw.input_tokens);
|
|
197
|
+
state.turnUsage.input += raw.input_tokens - cached;
|
|
198
|
+
state.turnUsage.cached += cached;
|
|
199
|
+
state.turnUsage.output += raw.output_tokens;
|
|
200
|
+
state.turnUsage.total += raw.total_tokens;
|
|
201
|
+
}
|
|
202
|
+
function updateCompactPostContext(state, raw) {
|
|
203
|
+
// 压缩后的水位:压缩收尾 token_count 全 0(不满足 zero-filter),
|
|
204
|
+
// 但 total_tokens 表达真实窗口;之后若出现普通 turn(input>0),
|
|
205
|
+
// 水位回归该 turn,post-compact 值失效。
|
|
206
|
+
if (state.compactCount <= 0 || !raw)
|
|
207
|
+
return;
|
|
208
|
+
if (raw.total_tokens && !raw.input_tokens && !raw.cached_input_tokens && !raw.output_tokens) {
|
|
209
|
+
state.compactPostContextLength = raw.total_tokens;
|
|
210
|
+
}
|
|
211
|
+
else if (raw.input_tokens > 0) {
|
|
212
|
+
state.compactPostContextLength = undefined;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function handleTokenCount(state, payload) {
|
|
216
|
+
const info = payload.info;
|
|
217
|
+
const { raw, total } = resolveRawUsage(info, state.previousTotals);
|
|
218
|
+
if (total) {
|
|
219
|
+
state.previousTotals = total;
|
|
220
|
+
state.lastTotalUsage = total;
|
|
221
|
+
}
|
|
222
|
+
if (raw && (raw.input_tokens || raw.cached_input_tokens || raw.output_tokens)) {
|
|
223
|
+
state.previousRawUsage = state.lastRawUsage;
|
|
224
|
+
state.lastRawUsage = raw;
|
|
225
|
+
const ctxWindow = info?.model_context_window;
|
|
226
|
+
state.lastContextLimit = typeof ctxWindow === 'number' ? ctxWindow : undefined;
|
|
227
|
+
accumulateTurnUsage(state, raw);
|
|
228
|
+
}
|
|
229
|
+
updateCompactPostContext(state, raw);
|
|
230
|
+
}
|
|
231
|
+
function handleEventMsg(state, payload) {
|
|
232
|
+
const human = extractRealUserText(payload);
|
|
233
|
+
if (human) {
|
|
234
|
+
recordHumanMessage(state, human);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (payload.type === 'task_started') {
|
|
238
|
+
// turn 边界:一个 turn 一条 task_started,从这里开始重新累计本 turn 的用量。
|
|
239
|
+
state.turnUsage = emptyTurnUsage();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (payload.type === 'token_count')
|
|
243
|
+
handleTokenCount(state, payload);
|
|
244
|
+
}
|
|
245
|
+
function handleCompacted(state, payload) {
|
|
246
|
+
// 顶层 compacted 事件:压缩次数 +1,压缩前水位取「压缩调用之前」那条非零
|
|
247
|
+
// token_count 的 total_tokens。真实 rollout 落盘顺序是
|
|
248
|
+
// 末轮 → 摘要调用 → compacted → 压缩后水位:摘要调用自身的 token_count 紧贴
|
|
249
|
+
// 在 compacted 之前,直接取 lastRawUsage 会把摘要调用的消耗当成压缩前窗口。
|
|
250
|
+
// compacted 自带的摘要调用 usage 用于判定并回退一条;旧数据无该字段时保持原语义。
|
|
251
|
+
state.compactCount++;
|
|
252
|
+
const compactCallTotal = readCompactedCallTotal(payload);
|
|
253
|
+
const preCompactUsage = compactCallTotal !== undefined && state.lastRawUsage?.total_tokens === compactCallTotal
|
|
254
|
+
? state.previousRawUsage
|
|
255
|
+
: state.lastRawUsage;
|
|
256
|
+
if (preCompactUsage?.total_tokens !== undefined) {
|
|
257
|
+
state.compactPreContextLength = preCompactUsage.total_tokens;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function handleResponseItem(state, payload, timestamp) {
|
|
261
|
+
if (payload.type !== 'message' || !Array.isArray(payload.content))
|
|
262
|
+
return;
|
|
263
|
+
if (payload.role === 'user')
|
|
264
|
+
state.userResponseItemCount++;
|
|
265
|
+
const messages = extractMessageContent(payload);
|
|
266
|
+
for (const msg of messages) {
|
|
267
|
+
state.events.push({
|
|
268
|
+
type: msg.role === 'user' ? 'user' : 'assistant',
|
|
269
|
+
content: msg.text,
|
|
270
|
+
timestamp,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function dispatchRolloutLine(state, parsed, includeEvents) {
|
|
275
|
+
if (parsed.type === 'session_meta' && parsed.payload) {
|
|
276
|
+
state.sessionMeta = parsed.payload;
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (parsed.type === 'event_msg' && parsed.payload) {
|
|
280
|
+
handleEventMsg(state, parsed.payload);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (parsed.type === 'compacted') {
|
|
284
|
+
handleCompacted(state, parsed.payload);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (includeEvents && parsed.type === 'response_item' && parsed.payload) {
|
|
288
|
+
handleResponseItem(state, parsed.payload, parsed.timestamp);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
120
291
|
/**
|
|
121
292
|
* Scan a rollout file once, tracking user messages, usage and compaction state.
|
|
122
293
|
* When `includeEvents` is true, response_item content is also extracted into
|
|
123
294
|
* `events`; when false, response_item lines are skipped entirely (the summary
|
|
124
295
|
* fast path).
|
|
296
|
+
*
|
|
297
|
+
* 真人输入只认 `user_message` / `item_completed.UserMessage`;`role:"user"`
|
|
298
|
+
* response_item 是注入 scaffolding,只用于漂移探针,不进 displayTitle。
|
|
125
299
|
*/
|
|
126
300
|
function scanCodexRollout(filePath, includeEvents) {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
let sessionMeta = null;
|
|
130
|
-
// Real user input comes from the authoritative "human typed this" event —
|
|
131
|
-
// `user_message` on old codex, `item_completed`/UserMessage on new codex
|
|
132
|
-
// (see `extractRealUserText`). Injected scaffolding (project rules,
|
|
133
|
-
// <environment_context>, permissions) is written as `role:"user"`
|
|
134
|
-
// response_items but gets NEITHER event, so it stays excluded from
|
|
135
|
-
// displayTitle/recap/summary. Regression 2026-07-13: the first `role:user`
|
|
136
|
-
// message is the injected project rules, mistakenly shown as "最近输入".
|
|
137
|
-
const realUserMessages = [];
|
|
138
|
-
// Which channel produced the last pushed message — used only to collapse a
|
|
139
|
-
// hypothetical double-write (same turn logged by both the legacy and the
|
|
140
|
-
// item channel), never to drop real consecutive input.
|
|
141
|
-
let lastUserMessageSource = null;
|
|
142
|
-
// Drift probe: count of `role:"user"` response_items (injected scaffolding
|
|
143
|
-
// included). If a file has those but yields zero real user messages, the
|
|
144
|
-
// event vocabulary changed again and displayTitle silently went empty —
|
|
145
|
-
// exactly the 0.153 compatibility incident shape.
|
|
146
|
-
let userResponseItemCount = 0;
|
|
147
|
-
// token_count tracking: codex emits cumulative `total_token_usage` and an
|
|
148
|
-
// incremental `last_token_usage` **per sampling call** — one turn with tool
|
|
149
|
-
// round-trips logs several `token_count` lines. We want the LAST turn's usage
|
|
150
|
-
// = the sum of that turn's samplings (matches pi/opencode /resume "last turn"
|
|
151
|
-
// display semantics). Turn boundaries come from `task_started`. When
|
|
152
|
-
// last_token_usage is absent we derive it as total - previous_total.
|
|
153
|
-
let lastRawUsage;
|
|
154
|
-
/** 上一条被接受的 raw usage;compacted 分支回退一条时用(见下方注释)。 */
|
|
155
|
-
let previousRawUsage;
|
|
156
|
-
/**
|
|
157
|
-
* 末个 turn 的累计用量;`null` = 文件里没有 `task_started`(旧数据),
|
|
158
|
-
* 此时 flow 字段退回「末条 token_count」语义(见 buildUsageFromScan)。
|
|
159
|
-
*/
|
|
160
|
-
let turnUsage = null;
|
|
161
|
-
// Context window limit from the same token_count event as lastRawUsage
|
|
162
|
-
// (codex reports info.model_context_window per turn; absent on old data).
|
|
163
|
-
let lastContextLimit;
|
|
164
|
-
// Final total_token_usage (cumulative across the whole session) for the
|
|
165
|
-
// Run card's "累计" display. Codex emits this alongside last_token_usage.
|
|
166
|
-
let lastTotalUsage;
|
|
167
|
-
let previousTotals;
|
|
168
|
-
// Compaction 统计:codex 的 compact turn 在会话文件里写顶层 `compacted`
|
|
169
|
-
// 事件(含摘要),压缩前后的上下文水位由相邻 token_count 表达。压缩收尾的
|
|
170
|
-
// token_count 增量是 input/cached/output 全 0、只有 total_tokens 有值
|
|
171
|
-
// (窗口被摘要+replacement history 占据),现有 zero-filter 会跳过它,
|
|
172
|
-
// 因此这里必须单独捕获,不能复用 lastRawUsage。
|
|
173
|
-
// 单位口径:前后两侧统一用 total_tokens —— 压缩前取最近一次
|
|
174
|
-
// 非零 token_count 的 total_tokens(input+output,即该 turn 在窗口内的全部
|
|
175
|
-
// token),压缩后取收尾事件的 total_tokens(压缩后窗口)。不得一边 input
|
|
176
|
-
// 一边 total,否则「压缩前 X → 压缩后 Y」两边单位不一致。
|
|
177
|
-
let compactCount = 0;
|
|
178
|
-
let compactPreContextLength;
|
|
179
|
-
let compactPostContextLength;
|
|
180
|
-
for (const line of lines) {
|
|
301
|
+
const state = newScanState();
|
|
302
|
+
for (const line of readJsonlLines(filePath)) {
|
|
181
303
|
const trimmed = line.trim();
|
|
182
304
|
if (!trimmed)
|
|
183
305
|
continue;
|
|
@@ -185,138 +307,15 @@ function scanCodexRollout(filePath, includeEvents) {
|
|
|
185
307
|
const parsed = JSON.parse(trimmed);
|
|
186
308
|
if (!parsed || typeof parsed.type !== 'string')
|
|
187
309
|
continue;
|
|
188
|
-
|
|
189
|
-
sessionMeta = parsed.payload;
|
|
190
|
-
}
|
|
191
|
-
else if (parsed.type === 'event_msg' && parsed.payload) {
|
|
192
|
-
const payload = parsed.payload;
|
|
193
|
-
const human = extractRealUserText(payload);
|
|
194
|
-
if (human) {
|
|
195
|
-
const text = human.text.slice(0, 200);
|
|
196
|
-
const prev = realUserMessages[realUserMessages.length - 1];
|
|
197
|
-
// 过渡期防御:同一 turn 若被旧版 user_message 与新版 item_completed
|
|
198
|
-
// 各写一次,会得到相邻重复。仅在「通道切换且文本与上一条完全相同」
|
|
199
|
-
// 时跳过——同通道连续相同输入(真人连发两条一样的话)不受影响。
|
|
200
|
-
const duplicateAcrossChannels = lastUserMessageSource !== null &&
|
|
201
|
-
lastUserMessageSource !== human.source &&
|
|
202
|
-
prev === text;
|
|
203
|
-
if (!duplicateAcrossChannels) {
|
|
204
|
-
realUserMessages.push(text);
|
|
205
|
-
}
|
|
206
|
-
lastUserMessageSource = human.source;
|
|
207
|
-
}
|
|
208
|
-
else if (payload.type === 'task_started') {
|
|
209
|
-
// turn 边界:一个 turn 一条 task_started(与 task_complete 成对,
|
|
210
|
-
// turn_id 见 session_meta 之后的 `turn_context`)。从这里开始重新
|
|
211
|
-
// 累计本 turn 的用量。
|
|
212
|
-
turnUsage = emptyTurnUsage();
|
|
213
|
-
}
|
|
214
|
-
else if (payload.type === 'token_count') {
|
|
215
|
-
const info = payload.info;
|
|
216
|
-
const last = info?.last_token_usage;
|
|
217
|
-
const total = info?.total_token_usage;
|
|
218
|
-
let raw;
|
|
219
|
-
if (last) {
|
|
220
|
-
raw = last;
|
|
221
|
-
}
|
|
222
|
-
else if (total && previousTotals) {
|
|
223
|
-
raw = subtractRawUsage(total, previousTotals);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
raw = total;
|
|
227
|
-
}
|
|
228
|
-
if (total) {
|
|
229
|
-
previousTotals = total;
|
|
230
|
-
lastTotalUsage = total;
|
|
231
|
-
}
|
|
232
|
-
if (raw && (raw.input_tokens || raw.cached_input_tokens || raw.output_tokens)) {
|
|
233
|
-
previousRawUsage = lastRawUsage;
|
|
234
|
-
lastRawUsage = raw;
|
|
235
|
-
const ctxWindow = info?.model_context_window;
|
|
236
|
-
lastContextLimit = typeof ctxWindow === 'number' ? ctxWindow : undefined;
|
|
237
|
-
if (turnUsage) {
|
|
238
|
-
const cached = Math.min(raw.cached_input_tokens, raw.input_tokens);
|
|
239
|
-
turnUsage.input += raw.input_tokens - cached;
|
|
240
|
-
turnUsage.cached += cached;
|
|
241
|
-
turnUsage.output += raw.output_tokens;
|
|
242
|
-
turnUsage.total += raw.total_tokens;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// 压缩后的水位:压缩收尾 token_count 全 0(raw 不满足上面 zero-filter),
|
|
246
|
-
// 但 total_tokens 表达真实窗口;之后若出现普通 turn(input>0),
|
|
247
|
-
// 水位回归该 turn,post-compact 值失效。
|
|
248
|
-
if (compactCount > 0 && raw) {
|
|
249
|
-
if (raw.total_tokens &&
|
|
250
|
-
!raw.input_tokens &&
|
|
251
|
-
!raw.cached_input_tokens &&
|
|
252
|
-
!raw.output_tokens) {
|
|
253
|
-
compactPostContextLength = raw.total_tokens;
|
|
254
|
-
}
|
|
255
|
-
else if (raw.input_tokens > 0) {
|
|
256
|
-
compactPostContextLength = undefined;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else if (parsed.type === 'compacted') {
|
|
262
|
-
// 顶层 compacted 事件:压缩次数 +1,压缩前水位取「压缩调用之前」那条非零
|
|
263
|
-
// token_count 的 total_tokens。真实 rollout 落盘顺序是
|
|
264
|
-
// 末轮 → 摘要调用 → compacted → 压缩后水位:摘要调用自身的 token_count 紧贴
|
|
265
|
-
// 在 compacted 之前,直接取 lastRawUsage 会把摘要调用的消耗当成压缩前窗口
|
|
266
|
-
// (实测把 363K 的窗口显示成 207K)。compacted 自带的摘要调用 usage 用于判定
|
|
267
|
-
// 并回退一条;旧数据没有 latest_token_usage_record 时保持原语义。
|
|
268
|
-
compactCount++;
|
|
269
|
-
const compactCallTotal = readCompactedCallTotal(parsed.payload);
|
|
270
|
-
const preCompactUsage = compactCallTotal !== undefined && lastRawUsage?.total_tokens === compactCallTotal
|
|
271
|
-
? previousRawUsage
|
|
272
|
-
: lastRawUsage;
|
|
273
|
-
if (preCompactUsage?.total_tokens !== undefined) {
|
|
274
|
-
compactPreContextLength = preCompactUsage.total_tokens;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
else if (includeEvents && parsed.type === 'response_item' && parsed.payload) {
|
|
278
|
-
const payload = parsed.payload;
|
|
279
|
-
if (payload.type === 'message' && Array.isArray(payload.content)) {
|
|
280
|
-
if (payload.role === 'user')
|
|
281
|
-
userResponseItemCount++;
|
|
282
|
-
// Extract text content for display
|
|
283
|
-
const messages = extractMessageContent(payload);
|
|
284
|
-
for (const msg of messages) {
|
|
285
|
-
events.push({
|
|
286
|
-
type: msg.role === 'user' ? 'user' : 'assistant',
|
|
287
|
-
content: msg.text,
|
|
288
|
-
timestamp: parsed.timestamp,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
310
|
+
dispatchRolloutLine(state, parsed, includeEvents);
|
|
293
311
|
}
|
|
294
312
|
catch {
|
|
295
313
|
// Skip malformed lines
|
|
296
314
|
continue;
|
|
297
315
|
}
|
|
298
316
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// 只告警不兜底:误用注入文本当标题正是 2026-07-13 修掉的缺陷。
|
|
302
|
-
if (realUserMessages.length === 0 && userResponseItemCount > 0) {
|
|
303
|
-
getLogger().warn(`[codex-rollout-reader] no human user message recognised in ${filePath} ` +
|
|
304
|
-
`(${userResponseItemCount} role:"user" response_items seen) — codex rollout ` +
|
|
305
|
-
`event vocabulary may have changed; session title will be empty`);
|
|
306
|
-
}
|
|
307
|
-
return {
|
|
308
|
-
sessionMeta,
|
|
309
|
-
events,
|
|
310
|
-
realUserMessages,
|
|
311
|
-
lastRawUsage,
|
|
312
|
-
lastContextLimit,
|
|
313
|
-
lastTotalUsage,
|
|
314
|
-
previousTotals,
|
|
315
|
-
turnUsage,
|
|
316
|
-
compactCount,
|
|
317
|
-
compactPreContextLength,
|
|
318
|
-
compactPostContextLength,
|
|
319
|
-
};
|
|
317
|
+
warnIfVocabularyDrift(state, filePath);
|
|
318
|
+
return scanResult(state);
|
|
320
319
|
}
|
|
321
320
|
/**
|
|
322
321
|
* Build ccusage-aligned usage from a scan's last-turn raw tokens.
|
|
@@ -720,7 +719,7 @@ export function clearSessionIndexCache() {
|
|
|
720
719
|
* event. Codex emitted it ONLY for text the human actually typed; injected
|
|
721
720
|
* scaffolding (project rules, `<environment_context>`, permissions) is
|
|
722
721
|
* written as `role:"user"` response_items but gets NO such event, so it stays
|
|
723
|
-
* out of displayTitle/recap/summary. (
|
|
722
|
+
* out of displayTitle/recap/summary. (Without this, the first
|
|
724
723
|
* `role:user` message is the injected project rules, once shown as 最近输入.)
|
|
725
724
|
* - Current (CLI ≥0.153.4, transitional from 0.147.0): the `user_message` /
|
|
726
725
|
* `agent_message` events were removed; the same authoritative text now rides
|
|
@@ -729,7 +728,7 @@ export function clearSessionIndexCache() {
|
|
|
729
728
|
* input, `skill` parts are injected skill files (e.g. a `$skill-name`
|
|
730
729
|
* invocation) that must not leak into the title.
|
|
731
730
|
*
|
|
732
|
-
* Real-data note
|
|
731
|
+
* Real-data note: no rollout file observed carries BOTH spellings;
|
|
733
732
|
* 0.147–0.149 are transitional (either spelling per file), 0.153.4+/0.154.0 are
|
|
734
733
|
* item_completed-only. `source` reports which spelling matched so the scanner
|
|
735
734
|
* can collapse a hypothetical double-write of the same turn.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session list 故障路径的统一出口:error 日志 + 抛契约文案 + cause 保留。
|
|
3
|
+
*
|
|
4
|
+
* 契约红线:错误文案固定为 `<agent> session list 读取失败: <msg>`(逐字节),
|
|
5
|
+
* router 靠它展示「列表失败」——静默 [] 会让用户误以为没有 session,
|
|
6
|
+
* 与真空不可区分。
|
|
7
|
+
*
|
|
8
|
+
* 各家 ENOENT → 优雅空态的分支是正常路径,留在调用点,不经过这里。
|
|
9
|
+
*/
|
|
10
|
+
export declare function throwSessionListError(agent: string, err: unknown): never;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getLogger } from '../../logger/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* session list 故障路径的统一出口:error 日志 + 抛契约文案 + cause 保留。
|
|
4
|
+
*
|
|
5
|
+
* 契约红线:错误文案固定为 `<agent> session list 读取失败: <msg>`(逐字节),
|
|
6
|
+
* router 靠它展示「列表失败」——静默 [] 会让用户误以为没有 session,
|
|
7
|
+
* 与真空不可区分。
|
|
8
|
+
*
|
|
9
|
+
* 各家 ENOENT → 优雅空态的分支是正常路径,留在调用点,不经过这里。
|
|
10
|
+
*/
|
|
11
|
+
export function throwSessionListError(agent, err) {
|
|
12
|
+
getLogger().error(`[${agent}-session-reader] session list failed: ${err}`);
|
|
13
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14
|
+
throw new Error(`${agent} session list 读取失败: ${msg}`, { cause: err });
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=throw-session-list-error.js.map
|