lark-remote 0.5.13 → 0.5.14
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 +15 -0
- package/dist/agent-kinds.d.ts +27 -0
- package/dist/agent-kinds.js +38 -0
- package/dist/bridge/approval-actions.d.ts +95 -0
- package/dist/bridge/approval-actions.js +104 -0
- package/dist/bridge/final-usage.d.ts +8 -17
- package/dist/bridge/final-usage.js +1 -18
- package/dist/bridge/index.d.ts +46 -39
- package/dist/bridge/index.js +201 -229
- package/dist/bridge/queue-manager.d.ts +25 -1
- package/dist/bridge/queue-manager.js +179 -160
- package/dist/bridge/run-approval.d.ts +2 -1
- package/dist/bridge/run-usage.d.ts +16 -30
- package/dist/bridge/run-usage.js +41 -50
- package/dist/bridge/session-writeback.d.ts +3 -2
- package/dist/card/approval-render.d.ts +3 -2
- package/dist/card/approval-render.js +3 -2
- package/dist/{router → card}/card-helpers.d.ts +7 -7
- package/dist/{router → card}/card-helpers.js +8 -8
- package/dist/card/card-session.d.ts +4 -2
- package/dist/card/card-session.js +8 -6
- package/dist/card/card-shared.d.ts +1 -1
- package/dist/card/card-shared.js +1 -1
- package/dist/card/final-answer-overflow.d.ts +0 -6
- package/dist/card/final-answer-overflow.js +1 -1
- package/dist/card/run-card-session.d.ts +2 -2
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.js +7 -22
- package/dist/card/run-state.d.ts +28 -50
- package/dist/card/run-state.js +23 -0
- package/dist/card/usage-format.d.ts +30 -0
- package/dist/{router → card}/usage-format.js +3 -10
- package/dist/clone.js +6 -1
- package/dist/config/index.d.ts +16 -20
- package/dist/config/index.js +0 -16
- package/dist/config/pi-config.js +6 -4
- package/dist/connector/index.d.ts +2 -4
- package/dist/connector/index.js +7 -11
- package/dist/{runner/dsh → dsh}/auth.js +1 -1
- package/dist/dsh/protocol.d.ts +9 -0
- package/dist/dsh/protocol.js +61 -0
- package/dist/index.js +10 -5
- package/dist/model-catalog.d.ts +14 -0
- package/dist/model-catalog.js +25 -0
- package/dist/platform/path.d.ts +9 -0
- package/dist/platform/path.js +18 -0
- package/dist/router/active-run-usage.d.ts +4 -0
- package/dist/router/active-run-usage.js +12 -0
- package/dist/router/commands/ls.js +1 -1
- package/dist/router/commands/order.js +1 -1
- package/dist/router/commands/resume.d.ts +5 -4
- package/dist/router/commands/resume.js +4 -4
- package/dist/router/commands/ws.js +1 -1
- package/dist/router/config/claude.js +2 -1
- package/dist/router/config/common/render.d.ts +5 -1
- package/dist/router/config/common/render.js +6 -13
- package/dist/router/config/dsh.d.ts +1 -1
- package/dist/router/config/index.d.ts +1 -1
- package/dist/router/config/types.d.ts +11 -3
- package/dist/router/index.d.ts +41 -11
- package/dist/router/index.js +165 -226
- package/dist/runner/agent-choices-common.d.ts +1 -1
- package/dist/runner/claude/runner.d.ts +20 -2
- package/dist/runner/claude/runner.js +2 -15
- package/dist/runner/codex/app-server/runner.d.ts +3 -1
- package/dist/runner/codex/app-server/translator.d.ts +11 -0
- package/dist/runner/codex/app-server/translator.js +56 -149
- package/dist/runner/common/acp/base-acp-runner.d.ts +3 -1
- package/dist/runner/common/connection-based-runner.d.ts +3 -1
- package/dist/runner/common/jsonrpc/transport.js +17 -7
- package/dist/runner/dsh/client.d.ts +1 -4
- package/dist/runner/dsh/client.js +2 -55
- package/dist/runner/dsh/daemon.js +9 -2
- package/dist/runner/dsh/exit-cancel.js +1 -1
- package/dist/runner/dsh/mux-stream.js +21 -2
- package/dist/runner/dsh/runner.d.ts +21 -1
- package/dist/runner/dsh/runner.js +76 -73
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +22 -1
- package/dist/runner/kimi/acp/runner.js +83 -60
- package/dist/runner/opencode/acp/error-monitor.d.ts +11 -0
- package/dist/runner/opencode/acp/error-monitor.js +31 -2
- package/dist/runner/opencode/acp/runner.d.ts +3 -1
- package/dist/runner/pi/rpc/runner.d.ts +2 -1
- package/dist/runner/probe.d.ts +1 -1
- package/dist/runner/probe.js +10 -9
- package/dist/runner/registry.d.ts +2 -1
- package/dist/runner/types.d.ts +20 -154
- package/dist/runner/types.js +4 -24
- package/dist/session/claude/session-index.d.ts +11 -4
- package/dist/session/claude/session-index.js +30 -8
- package/dist/session/claude/session-reader.d.ts +1 -1
- package/dist/session/claude/sessions.d.ts +2 -2
- package/dist/session/claude/sessions.js +41 -26
- package/dist/session/codex/rollout-reader.d.ts +1 -1
- package/dist/session/codex/rollout-reader.js +14 -13
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +4 -2
- package/dist/session/common/jsonl.js +7 -1
- package/dist/session/common/pagination.d.ts +2 -0
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +6 -0
- package/dist/session/common/recency.js +7 -5
- package/dist/session/common/two-pass.d.ts +1 -1
- package/dist/session/common/usage-accumulator.d.ts +1 -1
- package/dist/session/dsh/sessions.d.ts +1 -1
- package/dist/session/dsh/sessions.js +37 -12
- package/dist/session/kimi/sessions.d.ts +1 -1
- package/dist/session/kimi/sessions.js +35 -8
- package/dist/session/opencode/sessions.d.ts +1 -1
- package/dist/session/opencode/sessions.js +51 -47
- package/dist/session/pi/sessions.d.ts +1 -1
- package/dist/session/pi/sessions.js +20 -7
- package/dist/session/registry.d.ts +2 -1
- package/dist/session/session-store.d.ts +1 -2
- package/dist/session/session-store.js +14 -7
- package/dist/session-contract.d.ts +178 -0
- package/dist/session-contract.js +9 -0
- package/package.json +1 -1
- package/dist/router/usage-format.d.ts +0 -60
- /package/dist/{runner/dsh → dsh}/auth.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/home.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/home.js +0 -0
- /package/dist/{runner/dsh → dsh}/types.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/types.js +0 -0
|
@@ -144,6 +144,11 @@ export class QueueManager {
|
|
|
144
144
|
/**
|
|
145
145
|
* Enqueue a task into the workspace-level serial queue.
|
|
146
146
|
* Each cwd has its own serial queue for parallel execution across workspaces.
|
|
147
|
+
*
|
|
148
|
+
* 三段各自的职责:`registerQueuedTask`(排队元数据 + 满队拒收)、
|
|
149
|
+
* `beginQueuedTask`(接跑时刻的撤销检查/替换闭包/占用登记)、本方法
|
|
150
|
+
* (promise 链拼接与 settle 记账)。红线:链的拼接必须同步完成,
|
|
151
|
+
* 中途不得 await(否则两条入队会乱序)。
|
|
147
152
|
*/
|
|
148
153
|
enqueue(cwd, task, opts) {
|
|
149
154
|
// Guard: reject non-function tasks that would poison the queue chain
|
|
@@ -156,77 +161,9 @@ export class QueueManager {
|
|
|
156
161
|
// interrupt credit granted by `resetExecutingCount` can only be consumed
|
|
157
162
|
// by the interrupted task's own settle.
|
|
158
163
|
const slotId = ++this.slotCounter;
|
|
159
|
-
// Track this task in the queue for display card
|
|
160
164
|
const taskMeta = opts?.taskMeta;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
messagePreview = taskMeta.messagePreview;
|
|
164
|
-
const queuedTask = {
|
|
165
|
-
userId: taskMeta.userId,
|
|
166
|
-
chatId: taskMeta.chatId,
|
|
167
|
-
messageId: taskMeta.messageId,
|
|
168
|
-
feishuReplyTo: taskMeta.feishuReplyTo,
|
|
169
|
-
cwd,
|
|
170
|
-
timestamp: Date.now(),
|
|
171
|
-
messagePreview,
|
|
172
|
-
editable: taskMeta.editable,
|
|
173
|
-
binding: taskMeta.binding,
|
|
174
|
-
};
|
|
175
|
-
// Check if there are tasks waiting BEFORE adding this one
|
|
176
|
-
const currentExecutingCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
177
|
-
const currentQueueLength = this.queuedTasks.get(cwd)?.length ?? 0;
|
|
178
|
-
const hasWaitingTasks = currentExecutingCount > 0 || currentQueueLength > 0;
|
|
179
|
-
// bound the waiting backlog. When the queue is full, reject the
|
|
180
|
-
// task with a visible card instead of appending it (which would grow
|
|
181
|
-
// the promise chain + closures unboundedly under a message flood and
|
|
182
|
-
// fire another queue card). Do NOT increment pendingOrExecutingCount —
|
|
183
|
-
// a rejected task never executes, so it must not occupy a slot.
|
|
184
|
-
if (currentQueueLength >= MAX_WAITING_QUEUE) {
|
|
185
|
-
getLogger().warn(`[queue-manager] queue full cwd=${cwd} depth=${currentQueueLength} rejecting task messageId=${taskMeta.messageId}`);
|
|
186
|
-
void this.sendCard(taskMeta.chatId, cardWithHeader({
|
|
187
|
-
template: 'red',
|
|
188
|
-
title: '🚫 队列已满',
|
|
189
|
-
elements: [
|
|
190
|
-
{
|
|
191
|
-
tag: 'div',
|
|
192
|
-
text: {
|
|
193
|
-
tag: 'lark_md',
|
|
194
|
-
content: `当前工作目录已有 ${MAX_WAITING_QUEUE} 条消息排队等待,为防止积压已拒收本条消息。请等待队列消化后重发,或用 \`/stop\` 清空当前任务。`,
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
}), { replyTo: taskMeta.feishuReplyTo ?? taskMeta.messageId }).catch((err) => {
|
|
199
|
-
getLogger().error('[queue-manager] failed to send queue-full rejection card:', err);
|
|
200
|
-
});
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
// Add to cwd queue list
|
|
204
|
-
let taskList = this.queuedTasks.get(cwd);
|
|
205
|
-
if (!taskList) {
|
|
206
|
-
taskList = [];
|
|
207
|
-
this.queuedTasks.set(cwd, taskList);
|
|
208
|
-
}
|
|
209
|
-
// 身份不变量 tripwire:同一 cwd 内 queue key 必须唯一(一次入队动作一个
|
|
210
|
-
// key,铸造点 router.mintQueueTaskKey)。撞 key 时数组(追加)与 taskIndex
|
|
211
|
-
// (覆盖)必然漂移,位置显示/撤销/立即执行/卡片更新会串到别的条目上——
|
|
212
|
-
// 2026-10-08 线上缺陷就是这么长出来的。这里不改行为,只留证据。
|
|
213
|
-
if (this.indexGet(cwd, queuedTask.messageId)) {
|
|
214
|
-
getLogger().warn(`[queue-manager] duplicate queue key cwd=${cwd} messageId=${queuedTask.messageId} — ` +
|
|
215
|
-
'queue identity must be minted per enqueue action (see router.mintQueueTaskKey)');
|
|
216
|
-
}
|
|
217
|
-
taskList.push(queuedTask);
|
|
218
|
-
this.indexAdd(cwd, queuedTask);
|
|
219
|
-
// Increment executing count SYNCHRONOUSLY so subsequent enqueues see it
|
|
220
|
-
this.pendingOrExecutingCount.set(cwd, currentExecutingCount + 1);
|
|
221
|
-
// Only send the queue card if the task actually has to wait.
|
|
222
|
-
if (hasWaitingTasks) {
|
|
223
|
-
void this.sendQueueStatusCard(cwd, taskMeta.chatId, taskMeta.messageId, messagePreview,
|
|
224
|
-
// Reply to the real Feishu message id when the queue key is an
|
|
225
|
-
// internal key (order.exec); otherwise messageId IS the Feishu id.
|
|
226
|
-
taskMeta.feishuReplyTo);
|
|
227
|
-
}
|
|
228
|
-
getLogger().debug(`[queue-manager] enqueue task queued cwd=${cwd} queueCard=${hasWaitingTasks} executing=${currentExecutingCount + 1} queueLen=${currentQueueLength + 1}`);
|
|
229
|
-
}
|
|
165
|
+
if (taskMeta && !this.registerQueuedTask(cwd, taskMeta))
|
|
166
|
+
return;
|
|
230
167
|
// Get or create the queue for this cwd
|
|
231
168
|
let queue = this.queues.get(cwd);
|
|
232
169
|
if (!queue) {
|
|
@@ -236,97 +173,8 @@ export class QueueManager {
|
|
|
236
173
|
getLogger().debug(`[queue-manager] enqueue cwd=${cwd}`);
|
|
237
174
|
// Capture messageId for cancellation guard
|
|
238
175
|
const messageId = taskMeta?.messageId;
|
|
239
|
-
// messagePreview already captured above (outside taskMeta block)
|
|
240
176
|
const newQueue = queue
|
|
241
|
-
.then(() =>
|
|
242
|
-
getLogger().debug(`[queue-manager] task begin cwd=${cwd} messageId=${messageId}`);
|
|
243
|
-
// Live preview for the executing card: read from the QueuedTask at
|
|
244
|
-
// begin time, not the enqueue-closure `taskMeta` (which is frozen).
|
|
245
|
-
// `updateQueuedTaskMessage` (queue.edit/queue.input) mutates the live
|
|
246
|
-
// task, so a task edited while queued must show the edited content
|
|
247
|
-
// when it naturally starts.
|
|
248
|
-
let livePreview = taskMeta?.messagePreview ?? '';
|
|
249
|
-
// Check if this task was cancelled before executing
|
|
250
|
-
if (messageId) {
|
|
251
|
-
// O(1) index lookup replaces the old `find` + `findIndex`
|
|
252
|
-
// double O(N) scan. The ordered array still drives removal (splice
|
|
253
|
-
// preserves queue order for position display); the index stays in
|
|
254
|
-
// sync via indexRemove.
|
|
255
|
-
const task = this.indexGet(cwd, messageId);
|
|
256
|
-
if (!task) {
|
|
257
|
-
getLogger().debug(`[queue-manager] task skipped (cancelled) cwd=${cwd} messageId=${messageId}`);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
livePreview = task.messagePreview;
|
|
261
|
-
// Remove this task's metadata by messageId
|
|
262
|
-
const taskList = this.queuedTasks.get(cwd);
|
|
263
|
-
const idx = taskList?.findIndex((t) => t.messageId === messageId) ?? -1;
|
|
264
|
-
if (taskList && idx >= 0) {
|
|
265
|
-
taskList.splice(idx, 1);
|
|
266
|
-
}
|
|
267
|
-
this.indexRemove(cwd, messageId);
|
|
268
|
-
// Cancellation check passed and the task has been removed: it is
|
|
269
|
-
// about to run, so record it as began for queue.immediate feedback.
|
|
270
|
-
// Bounded: ids are only consulted for the current immediate target,
|
|
271
|
-
// so pruning the oldest entries is safe.
|
|
272
|
-
if (this.beganMessageIds.size >= 10_000) {
|
|
273
|
-
const oldest = this.beganMessageIds.values().next().value;
|
|
274
|
-
if (oldest !== undefined)
|
|
275
|
-
this.beganMessageIds.delete(oldest);
|
|
276
|
-
}
|
|
277
|
-
this.beganMessageIds.add(messageId);
|
|
278
|
-
}
|
|
279
|
-
// Consume any one-shot replacement closure registered by
|
|
280
|
-
// `setTaskReplacement` (queue.edit + queue.immediate). The replacement
|
|
281
|
-
// swaps in the edited content at this task's original queue slot, so
|
|
282
|
-
// tasks queued behind it still run after it. Deleted here (one-shot)
|
|
283
|
-
// so a stale closure can never run on a later task.
|
|
284
|
-
let replacement;
|
|
285
|
-
if (messageId) {
|
|
286
|
-
const workspaceReplacements = this.taskReplacements.get(cwd);
|
|
287
|
-
replacement = workspaceReplacements?.get(messageId);
|
|
288
|
-
if (replacement) {
|
|
289
|
-
workspaceReplacements?.delete(messageId);
|
|
290
|
-
if (workspaceReplacements && workspaceReplacements.size === 0) {
|
|
291
|
-
this.taskReplacements.delete(cwd);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
// Update queue card to "executing" status BEFORE running the task.
|
|
296
|
-
// This must happen after the cancellation check: a skipped/cancelled
|
|
297
|
-
// task must keep its "❌ 已撤销" card, not be flipped to executing.
|
|
298
|
-
if (messageId) {
|
|
299
|
-
void this.updateQueueCardToExecuting(cwd, messageId, livePreview, true);
|
|
300
|
-
}
|
|
301
|
-
// Re-arm the pending/executing count for every task that is about to
|
|
302
|
-
// run. `resetExecutingCount` (external interrupt) zeroes the count,
|
|
303
|
-
// and the interrupted task's settle consumes the skip-credit — so a
|
|
304
|
-
// task enqueued BEFORE the interrupt can begin with count 0. Without
|
|
305
|
-
// this, a running task is invisible to later enqueues and they
|
|
306
|
-
// silently skip the "⏳ 消息排队中" card. This applies regardless of
|
|
307
|
-
// taskMeta: resetExecutingCount can clear the count for any cwd,
|
|
308
|
-
// and a task that resumes after an interrupt must re-arm even when it
|
|
309
|
-
// carries no metadata.
|
|
310
|
-
const currentCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
311
|
-
if (currentCount < 1) {
|
|
312
|
-
this.pendingOrExecutingCount.set(cwd, 1);
|
|
313
|
-
getLogger().debug(`[queue-manager] re-armed pendingOrExecutingCount cwd=${cwd} (interrupt resume)`);
|
|
314
|
-
}
|
|
315
|
-
// Mark this task as the lane occupant for its whole execution period
|
|
316
|
-
// before running it. `resetExecutingCount` reads the slot id to grant
|
|
317
|
-
// the interrupt credit to exactly this task; the settle removes the
|
|
318
|
-
// record. `kind`/`label` let a later message's queue card name what it
|
|
319
|
-
// is waiting for instead of reporting "前面还有 0 条".
|
|
320
|
-
this.executingInfo.set(cwd, {
|
|
321
|
-
slotId,
|
|
322
|
-
messageId,
|
|
323
|
-
kind: taskMeta?.kind ?? 'message',
|
|
324
|
-
label: laneOccupantLabel(taskMeta?.kind ?? 'message', livePreview),
|
|
325
|
-
preview: livePreview,
|
|
326
|
-
startedAt: Date.now(),
|
|
327
|
-
});
|
|
328
|
-
return replacement ? replacement() : task();
|
|
329
|
-
})
|
|
177
|
+
.then(() => this.beginQueuedTask(cwd, slotId, messageId, task, taskMeta))
|
|
330
178
|
.then(() => {
|
|
331
179
|
getLogger().debug(`[queue-manager] task end cwd=${cwd}`);
|
|
332
180
|
// beganMessageIds entry intentionally retained: sticky by design
|
|
@@ -343,6 +191,177 @@ export class QueueManager {
|
|
|
343
191
|
});
|
|
344
192
|
this.queues.set(cwd, newQueue);
|
|
345
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* 登记排队元数据(展示卡数据源 + 计数 + 排队卡)。
|
|
196
|
+
*
|
|
197
|
+
* @returns false = 队列已满被拒收(不入队、不占执行槽位),调用方必须
|
|
198
|
+
* 直接把任务链跳过。背压红线:满队时拒收而不是继续 append,否则 promise
|
|
199
|
+
* 链与闭包在消息洪峰下无界增长,还会多打一张排队卡。
|
|
200
|
+
*/
|
|
201
|
+
registerQueuedTask(cwd, taskMeta) {
|
|
202
|
+
// Check if there are tasks waiting BEFORE adding this one
|
|
203
|
+
const currentExecutingCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
204
|
+
const currentQueueLength = this.queuedTasks.get(cwd)?.length ?? 0;
|
|
205
|
+
const hasWaitingTasks = currentExecutingCount > 0 || currentQueueLength > 0;
|
|
206
|
+
if (currentQueueLength >= MAX_WAITING_QUEUE) {
|
|
207
|
+
getLogger().warn(`[queue-manager] queue full cwd=${cwd} depth=${currentQueueLength} rejecting task messageId=${taskMeta.messageId}`);
|
|
208
|
+
void this.sendQueueFullRejection(taskMeta);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
const queuedTask = {
|
|
212
|
+
userId: taskMeta.userId,
|
|
213
|
+
chatId: taskMeta.chatId,
|
|
214
|
+
messageId: taskMeta.messageId,
|
|
215
|
+
feishuReplyTo: taskMeta.feishuReplyTo,
|
|
216
|
+
cwd,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
messagePreview: taskMeta.messagePreview,
|
|
219
|
+
editable: taskMeta.editable,
|
|
220
|
+
binding: taskMeta.binding,
|
|
221
|
+
};
|
|
222
|
+
// Add to cwd queue list
|
|
223
|
+
let taskList = this.queuedTasks.get(cwd);
|
|
224
|
+
if (!taskList) {
|
|
225
|
+
taskList = [];
|
|
226
|
+
this.queuedTasks.set(cwd, taskList);
|
|
227
|
+
}
|
|
228
|
+
// 身份不变量 tripwire:同一 cwd 内 queue key 必须唯一(一次入队动作一个
|
|
229
|
+
// key,铸造点 router.mintQueueTaskKey)。撞 key 时数组(追加)与 taskIndex
|
|
230
|
+
// (覆盖)必然漂移,位置显示/撤销/立即执行/卡片更新会串到别的条目上——
|
|
231
|
+
// 2026-10-08 线上缺陷就是这么长出来的。这里不改行为,只留证据。
|
|
232
|
+
if (this.indexGet(cwd, queuedTask.messageId)) {
|
|
233
|
+
getLogger().warn(`[queue-manager] duplicate queue key cwd=${cwd} messageId=${queuedTask.messageId} — ` +
|
|
234
|
+
'queue identity must be minted per enqueue action (see router.mintQueueTaskKey)');
|
|
235
|
+
}
|
|
236
|
+
taskList.push(queuedTask);
|
|
237
|
+
this.indexAdd(cwd, queuedTask);
|
|
238
|
+
// Increment executing count SYNCHRONOUSLY so subsequent enqueues see it
|
|
239
|
+
this.pendingOrExecutingCount.set(cwd, currentExecutingCount + 1);
|
|
240
|
+
// Only send the queue card if the task actually has to wait.
|
|
241
|
+
if (hasWaitingTasks) {
|
|
242
|
+
void this.sendQueueStatusCard(cwd, taskMeta.chatId, taskMeta.messageId, taskMeta.messagePreview,
|
|
243
|
+
// Reply to the real Feishu message id when the queue key is an
|
|
244
|
+
// internal key (order.exec); otherwise messageId IS the Feishu id.
|
|
245
|
+
taskMeta.feishuReplyTo);
|
|
246
|
+
}
|
|
247
|
+
getLogger().debug(`[queue-manager] enqueue task queued cwd=${cwd} queueCard=${hasWaitingTasks} executing=${currentExecutingCount + 1} queueLen=${currentQueueLength + 1}`);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
/** 满队拒收卡(best-effort 送达,失败只记日志:任务已经被拒,不能再吞一次反馈)。 */
|
|
251
|
+
async sendQueueFullRejection(taskMeta) {
|
|
252
|
+
await this.sendCard(taskMeta.chatId, cardWithHeader({
|
|
253
|
+
template: 'red',
|
|
254
|
+
title: '🚫 队列已满',
|
|
255
|
+
elements: [
|
|
256
|
+
{
|
|
257
|
+
tag: 'div',
|
|
258
|
+
text: {
|
|
259
|
+
tag: 'lark_md',
|
|
260
|
+
content: `当前工作目录已有 ${MAX_WAITING_QUEUE} 条消息排队等待,为防止积压已拒收本条消息。请等待队列消化后重发,或用 \`/stop\` 清空当前任务。`,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
}), { replyTo: taskMeta.feishuReplyTo ?? taskMeta.messageId }).catch((err) => {
|
|
265
|
+
getLogger().error('[queue-manager] failed to send queue-full rejection card:', err);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* 轮到本任务执行:撤销检查 → 消费一次性替换闭包 → 排队卡转「执行中」→
|
|
270
|
+
* 重新武装计数与 lane 占用登记 → 跑任务闭包。
|
|
271
|
+
*
|
|
272
|
+
* @returns 任务闭包的 promise;被撤销时返回 undefined(本槽位什么都不跑)。
|
|
273
|
+
*/
|
|
274
|
+
beginQueuedTask(cwd, slotId, messageId, task, taskMeta) {
|
|
275
|
+
getLogger().debug(`[queue-manager] task begin cwd=${cwd} messageId=${messageId}`);
|
|
276
|
+
// Live preview for the executing card: read from the QueuedTask at
|
|
277
|
+
// begin time, not the enqueue-closure `taskMeta` (which is frozen).
|
|
278
|
+
// `updateQueuedTaskMessage` (queue.edit/queue.input) mutates the live
|
|
279
|
+
// task, so a task edited while queued must show the edited content
|
|
280
|
+
// when it naturally starts.
|
|
281
|
+
let livePreview = taskMeta?.messagePreview ?? '';
|
|
282
|
+
// Check if this task was cancelled before executing
|
|
283
|
+
if (messageId) {
|
|
284
|
+
// O(1) index lookup replaces the old `find` + `findIndex`
|
|
285
|
+
// double O(N) scan. The ordered array still drives removal (splice
|
|
286
|
+
// preserves queue order for position display); the index stays in
|
|
287
|
+
// sync via indexRemove.
|
|
288
|
+
const queued = this.indexGet(cwd, messageId);
|
|
289
|
+
if (!queued) {
|
|
290
|
+
getLogger().debug(`[queue-manager] task skipped (cancelled) cwd=${cwd} messageId=${messageId}`);
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
livePreview = queued.messagePreview;
|
|
294
|
+
// Remove this task's metadata by messageId
|
|
295
|
+
const taskList = this.queuedTasks.get(cwd);
|
|
296
|
+
const idx = taskList?.findIndex((t) => t.messageId === messageId) ?? -1;
|
|
297
|
+
if (taskList && idx >= 0) {
|
|
298
|
+
taskList.splice(idx, 1);
|
|
299
|
+
}
|
|
300
|
+
this.indexRemove(cwd, messageId);
|
|
301
|
+
// Cancellation check passed and the task has been removed: it is
|
|
302
|
+
// about to run, so record it as began for queue.immediate feedback.
|
|
303
|
+
// Bounded: ids are only consulted for the current immediate target,
|
|
304
|
+
// so pruning the oldest entries is safe.
|
|
305
|
+
if (this.beganMessageIds.size >= 10_000) {
|
|
306
|
+
const oldest = this.beganMessageIds.values().next().value;
|
|
307
|
+
if (oldest !== undefined)
|
|
308
|
+
this.beganMessageIds.delete(oldest);
|
|
309
|
+
}
|
|
310
|
+
this.beganMessageIds.add(messageId);
|
|
311
|
+
}
|
|
312
|
+
// Consume any one-shot replacement closure registered by
|
|
313
|
+
// `setTaskReplacement` (queue.edit + queue.immediate). The replacement
|
|
314
|
+
// swaps in the edited content at this task's original queue slot, so
|
|
315
|
+
// tasks queued behind it still run after it. Deleted here (one-shot)
|
|
316
|
+
// so a stale closure can never run on a later task.
|
|
317
|
+
const replacement = messageId ? this.takeTaskReplacement(cwd, messageId) : undefined;
|
|
318
|
+
// Update queue card to "executing" status BEFORE running the task.
|
|
319
|
+
// This must happen after the cancellation check: a skipped/cancelled
|
|
320
|
+
// task must keep its "❌ 已撤销" card, not be flipped to executing.
|
|
321
|
+
if (messageId) {
|
|
322
|
+
void this.updateQueueCardToExecuting(cwd, messageId, livePreview, true);
|
|
323
|
+
}
|
|
324
|
+
// Re-arm the pending/executing count for every task that is about to
|
|
325
|
+
// run. `resetExecutingCount` (external interrupt) zeroes the count,
|
|
326
|
+
// and the interrupted task's settle consumes the skip-credit — so a
|
|
327
|
+
// task enqueued BEFORE the interrupt can begin with count 0. Without
|
|
328
|
+
// this, a running task is invisible to later enqueues and they
|
|
329
|
+
// silently skip the "⏳ 消息排队中" card. This applies regardless of
|
|
330
|
+
// taskMeta: resetExecutingCount can clear the count for any cwd,
|
|
331
|
+
// and a task that resumes after an interrupt must re-arm even when it
|
|
332
|
+
// carries no metadata.
|
|
333
|
+
const currentCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
334
|
+
if (currentCount < 1) {
|
|
335
|
+
this.pendingOrExecutingCount.set(cwd, 1);
|
|
336
|
+
getLogger().debug(`[queue-manager] re-armed pendingOrExecutingCount cwd=${cwd} (interrupt resume)`);
|
|
337
|
+
}
|
|
338
|
+
// Mark this task as the lane occupant for its whole execution period
|
|
339
|
+
// before running it. `resetExecutingCount` reads the slot id to grant
|
|
340
|
+
// the interrupt credit to exactly this task; the settle removes the
|
|
341
|
+
// record. `kind`/`label` let a later message's queue card name what it
|
|
342
|
+
// is waiting for instead of reporting "前面还有 0 条".
|
|
343
|
+
this.executingInfo.set(cwd, {
|
|
344
|
+
slotId,
|
|
345
|
+
messageId,
|
|
346
|
+
kind: taskMeta?.kind ?? 'message',
|
|
347
|
+
label: laneOccupantLabel(taskMeta?.kind ?? 'message', livePreview),
|
|
348
|
+
preview: livePreview,
|
|
349
|
+
startedAt: Date.now(),
|
|
350
|
+
});
|
|
351
|
+
return replacement ? replacement() : task();
|
|
352
|
+
}
|
|
353
|
+
/** 取走并清除该任务的一次性替换闭包(消费即销毁)。 */
|
|
354
|
+
takeTaskReplacement(cwd, messageId) {
|
|
355
|
+
const workspaceReplacements = this.taskReplacements.get(cwd);
|
|
356
|
+
const replacement = workspaceReplacements?.get(messageId);
|
|
357
|
+
if (replacement) {
|
|
358
|
+
workspaceReplacements?.delete(messageId);
|
|
359
|
+
if (workspaceReplacements && workspaceReplacements.size === 0) {
|
|
360
|
+
this.taskReplacements.delete(cwd);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return replacement;
|
|
364
|
+
}
|
|
346
365
|
/**
|
|
347
366
|
* Execute a task immediately without going through the queue.
|
|
348
367
|
* Used for / commands that should respond immediately.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AppConfig } from '../config/index.js';
|
|
2
|
-
import type { AgentKind
|
|
2
|
+
import type { AgentKind } from '../agent-kinds.js';
|
|
3
|
+
import type { Runner } from '../runner/index.js';
|
|
3
4
|
import type { RunCardSession } from '../card/run-card-session.js';
|
|
4
5
|
import { ApprovalCoordinator } from './approval-coordinator.js';
|
|
5
6
|
export declare function remindPendingApproval(connector: {
|
|
@@ -1,46 +1,32 @@
|
|
|
1
1
|
import type { ResultEvent } from '../runner/types.js';
|
|
2
2
|
import type { FinalUsageSnapshot } from './final-usage.js';
|
|
3
|
+
import type { UsageSnapshot } from '../session-contract.js';
|
|
3
4
|
export type UsageAuthority = 'live' | 'jsonl';
|
|
5
|
+
/**
|
|
6
|
+
* 一张 run 的用量记账:live 侧(result 事件 / compact_boundary)先收着,
|
|
7
|
+
* run 结束时与 jsonl 快照按权威来源合成一份 `UsageSnapshot`。
|
|
8
|
+
*
|
|
9
|
+
* live 侧字段清单只有 `UsageSnapshot` 一份:此前这里是 19 个 `final*` 平铺
|
|
10
|
+
* 字段逐字段搬运,加一个 usage 字段要改三处(§9.21 的一致性靠人记)。
|
|
11
|
+
*/
|
|
4
12
|
export declare class RunUsageCollector {
|
|
13
|
+
/** live 侧观察值(jsonl 读不到时的兜底)。 */
|
|
5
14
|
private contextLength?;
|
|
6
|
-
private finalContextLength?;
|
|
7
|
-
private finalContextLimit?;
|
|
8
|
-
private finalCompactCount?;
|
|
9
|
-
private finalCacheReadTokens?;
|
|
10
|
-
private finalCacheCreationTokens?;
|
|
11
|
-
private finalTotalTokens?;
|
|
12
15
|
private liveInput?;
|
|
13
16
|
private liveOutput?;
|
|
14
17
|
private liveContextLimit?;
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
-
private
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
private finalCumulativeCacheCreationTokens?;
|
|
22
|
-
private finalReasoningTokens?;
|
|
18
|
+
private liveCacheRead?;
|
|
19
|
+
private liveCacheCreation?;
|
|
20
|
+
private liveTotal?;
|
|
21
|
+
private liveReasoningTokens?;
|
|
22
|
+
/** finalize() 之后的最终口径;meta() 的唯一数据源。 */
|
|
23
|
+
private resolved;
|
|
23
24
|
resultNotSuccess: boolean;
|
|
24
25
|
get liveInputTokens(): number | undefined;
|
|
25
26
|
get liveOutputTokens(): number | undefined;
|
|
26
27
|
observeCompaction(postTokens: number): void;
|
|
27
28
|
observeResult(event: ResultEvent): void;
|
|
28
29
|
finalize(finalUsage: FinalUsageSnapshot | undefined, authority: UsageAuthority | undefined): void;
|
|
29
|
-
meta():
|
|
30
|
-
contextLength?: number;
|
|
31
|
-
contextLimit?: number;
|
|
32
|
-
compactCount?: number;
|
|
33
|
-
cacheReadTokens?: number;
|
|
34
|
-
cacheCreationTokens?: number;
|
|
35
|
-
totalTokens?: number;
|
|
36
|
-
inputTokens?: number;
|
|
37
|
-
outputTokens?: number;
|
|
38
|
-
cumulativeTotalTokens?: number;
|
|
39
|
-
cumulativeInputTokens?: number;
|
|
40
|
-
cumulativeOutputTokens?: number;
|
|
41
|
-
cumulativeCacheReadTokens?: number;
|
|
42
|
-
cumulativeCacheCreationTokens?: number;
|
|
43
|
-
reasoningTokens?: number;
|
|
44
|
-
};
|
|
30
|
+
meta(): UsageSnapshot;
|
|
45
31
|
logSummary(runId: string): string;
|
|
46
32
|
}
|
package/dist/bridge/run-usage.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { normalizeResultUsage } from '../runner/common/result-usage.js';
|
|
2
|
+
/**
|
|
3
|
+
* 一张 run 的用量记账:live 侧(result 事件 / compact_boundary)先收着,
|
|
4
|
+
* run 结束时与 jsonl 快照按权威来源合成一份 `UsageSnapshot`。
|
|
5
|
+
*
|
|
6
|
+
* live 侧字段清单只有 `UsageSnapshot` 一份:此前这里是 19 个 `final*` 平铺
|
|
7
|
+
* 字段逐字段搬运,加一个 usage 字段要改三处(§9.21 的一致性靠人记)。
|
|
8
|
+
*/
|
|
2
9
|
export class RunUsageCollector {
|
|
10
|
+
/** live 侧观察值(jsonl 读不到时的兜底)。 */
|
|
3
11
|
contextLength;
|
|
4
|
-
finalContextLength;
|
|
5
|
-
finalContextLimit;
|
|
6
|
-
finalCompactCount;
|
|
7
|
-
finalCacheReadTokens;
|
|
8
|
-
finalCacheCreationTokens;
|
|
9
|
-
finalTotalTokens;
|
|
10
12
|
liveInput;
|
|
11
13
|
liveOutput;
|
|
12
14
|
liveContextLimit;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
finalCumulativeCacheCreationTokens;
|
|
20
|
-
finalReasoningTokens;
|
|
15
|
+
liveCacheRead;
|
|
16
|
+
liveCacheCreation;
|
|
17
|
+
liveTotal;
|
|
18
|
+
liveReasoningTokens;
|
|
19
|
+
/** finalize() 之后的最终口径;meta() 的唯一数据源。 */
|
|
20
|
+
resolved = {};
|
|
21
21
|
resultNotSuccess = false;
|
|
22
22
|
get liveInputTokens() {
|
|
23
23
|
return this.liveInput;
|
|
@@ -39,54 +39,45 @@ export class RunUsageCollector {
|
|
|
39
39
|
this.liveOutput = usage.outputTokens;
|
|
40
40
|
this.liveContextLimit = usage.contextLimit;
|
|
41
41
|
if (usage.cacheReadTokens !== undefined)
|
|
42
|
-
this.
|
|
42
|
+
this.liveCacheRead = usage.cacheReadTokens;
|
|
43
43
|
if (usage.cacheCreationTokens !== undefined) {
|
|
44
|
-
this.
|
|
44
|
+
this.liveCacheCreation = usage.cacheCreationTokens;
|
|
45
45
|
}
|
|
46
46
|
if (usage.totalTokens !== undefined)
|
|
47
|
-
this.
|
|
47
|
+
this.liveTotal = usage.totalTokens;
|
|
48
48
|
if (usage.reasoningTokens !== undefined)
|
|
49
|
-
this.
|
|
49
|
+
this.liveReasoningTokens = usage.reasoningTokens;
|
|
50
50
|
}
|
|
51
51
|
finalize(finalUsage, authority) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
const resolved = {
|
|
53
|
+
// 从 jsonl 快照整体展开,新增 UsageSnapshot 字段无需再改这里。
|
|
54
|
+
...finalUsage,
|
|
55
|
+
contextLength: finalUsage?.contextLength ?? this.contextLength,
|
|
56
|
+
contextLimit: finalUsage?.contextLimit ?? this.liveContextLimit,
|
|
57
|
+
compactCount: finalUsage?.compactCount,
|
|
58
|
+
// result 事件里的 cache/total 两条路径都要带:jsonl 读得到时被覆盖,
|
|
59
|
+
// 读不到(或走 live flow scope)时它就是最终数字。
|
|
60
|
+
cacheReadTokens: this.liveCacheRead,
|
|
61
|
+
cacheCreationTokens: this.liveCacheCreation,
|
|
62
|
+
totalTokens: this.liveTotal,
|
|
63
|
+
reasoningTokens: this.liveReasoningTokens,
|
|
64
|
+
};
|
|
60
65
|
const useLiveFlowScope = authority !== 'jsonl' && this.liveInput !== undefined && this.liveOutput !== undefined;
|
|
61
66
|
if (useLiveFlowScope) {
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
resolved.inputTokens = this.liveInput;
|
|
68
|
+
resolved.outputTokens = this.liveOutput;
|
|
69
|
+
this.resolved = resolved;
|
|
64
70
|
return;
|
|
65
71
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
+
resolved.inputTokens = finalUsage?.inputTokens ?? this.liveInput;
|
|
73
|
+
resolved.outputTokens = finalUsage?.outputTokens ?? this.liveOutput;
|
|
74
|
+
resolved.cacheReadTokens = finalUsage?.cacheReadTokens ?? this.liveCacheRead;
|
|
75
|
+
resolved.cacheCreationTokens = finalUsage?.cacheCreationTokens ?? this.liveCacheCreation;
|
|
76
|
+
resolved.totalTokens = finalUsage?.totalTokens ?? this.liveTotal;
|
|
77
|
+
this.resolved = resolved;
|
|
72
78
|
}
|
|
73
79
|
meta() {
|
|
74
|
-
return
|
|
75
|
-
contextLength: this.finalContextLength,
|
|
76
|
-
contextLimit: this.finalContextLimit,
|
|
77
|
-
compactCount: this.finalCompactCount,
|
|
78
|
-
cacheReadTokens: this.finalCacheReadTokens,
|
|
79
|
-
cacheCreationTokens: this.finalCacheCreationTokens,
|
|
80
|
-
totalTokens: this.finalTotalTokens,
|
|
81
|
-
inputTokens: this.finalInputTokens,
|
|
82
|
-
outputTokens: this.finalOutputTokens,
|
|
83
|
-
cumulativeTotalTokens: this.finalCumulativeTotalTokens,
|
|
84
|
-
cumulativeInputTokens: this.finalCumulativeInputTokens,
|
|
85
|
-
cumulativeOutputTokens: this.finalCumulativeOutputTokens,
|
|
86
|
-
cumulativeCacheReadTokens: this.finalCumulativeCacheReadTokens,
|
|
87
|
-
cumulativeCacheCreationTokens: this.finalCumulativeCacheCreationTokens,
|
|
88
|
-
reasoningTokens: this.finalReasoningTokens,
|
|
89
|
-
};
|
|
80
|
+
return this.resolved;
|
|
90
81
|
}
|
|
91
82
|
logSummary(runId) {
|
|
92
83
|
const meta = this.meta();
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { AgentKind
|
|
1
|
+
import type { AgentKind } from '../agent-kinds.js';
|
|
2
|
+
import type { TurnStartedEvent } from '../runner/index.js';
|
|
2
3
|
import type { SessionStore } from '../session/index.js';
|
|
3
4
|
interface InitEvent {
|
|
4
5
|
session_id: string;
|
|
5
6
|
cwd?: string;
|
|
6
7
|
}
|
|
7
|
-
interface WritebackContext {
|
|
8
|
+
export interface WritebackContext {
|
|
8
9
|
sessionStore: SessionStore;
|
|
9
10
|
userId: string;
|
|
10
11
|
agentKind: AgentKind;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Uses CardKit 2.0 (no tag:"action" container — 200861 rule).
|
|
6
6
|
*/
|
|
7
7
|
import type { ApprovalView } from '../runner/types.js';
|
|
8
|
+
import { type RunTerminal } from './run-state.js';
|
|
8
9
|
/**
|
|
9
10
|
* Render the approval area for a run card.
|
|
10
11
|
* Returns an array of CardKit 2.0 elements to append to the card body.
|
|
@@ -12,7 +13,7 @@ import type { ApprovalView } from '../runner/types.js';
|
|
|
12
13
|
export declare function renderApprovalArea(approval: ApprovalView | undefined, opts?: {
|
|
13
14
|
expired?: boolean;
|
|
14
15
|
runId?: string;
|
|
15
|
-
terminal?:
|
|
16
|
+
terminal?: RunTerminal;
|
|
16
17
|
}): object[];
|
|
17
18
|
/**
|
|
18
19
|
* 紧凑审批区(run 卡 skeleton/truncatedSkeleton 兜底档专用):固定小体积的
|
|
@@ -26,5 +27,5 @@ export declare function renderApprovalArea(approval: ApprovalView | undefined, o
|
|
|
26
27
|
export declare function renderApprovalAreaCompact(approval: ApprovalView | undefined, opts?: {
|
|
27
28
|
expired?: boolean;
|
|
28
29
|
runId?: string;
|
|
29
|
-
terminal?:
|
|
30
|
+
terminal?: RunTerminal;
|
|
30
31
|
}): object[];
|
|
@@ -9,6 +9,7 @@ import { truncateMarkdownTables } from './text-truncate.js';
|
|
|
9
9
|
import { truncateUtf8 } from '../common/truncate.js';
|
|
10
10
|
import { cardButton } from './card-shared.js';
|
|
11
11
|
import { countCardElements } from './card-budget.js';
|
|
12
|
+
import { isRunActive } from './run-state.js';
|
|
12
13
|
/** Unique nonce for card button callbacks (SDK dedup + router validation). */
|
|
13
14
|
function mkNonce() {
|
|
14
15
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
@@ -156,9 +157,9 @@ export function renderApprovalAreaCompact(approval, opts) {
|
|
|
156
157
|
}
|
|
157
158
|
return elements;
|
|
158
159
|
}
|
|
159
|
-
/** 终态隐藏判定(renderApprovalArea 与 compact
|
|
160
|
+
/** 终态隐藏判定(renderApprovalArea 与 compact 共用):仍在推进的 run 不隐藏按钮。 */
|
|
160
161
|
function isTerminalHidden(opts) {
|
|
161
|
-
return !!opts?.terminal && opts.terminal
|
|
162
|
+
return !!opts?.terminal && !isRunActive(opts.terminal);
|
|
162
163
|
}
|
|
163
164
|
/**
|
|
164
165
|
* Render approval action buttons from the real protocol decision list.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* ls, ws, etc.).
|
|
2
|
+
* 卡片构造的共享工具(会话历史卡、分页条、搜索条、ls/ws 面板等)。
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 归属 card/:这些是纯建卡函数,此前住在 router/ 导致 card→router 与
|
|
5
|
+
* bridge→router 两条反向依赖。`markdownDiv` 从 `card/collapsible.ts` 复用,
|
|
6
|
+
* 会话内容相关的 builder(如 `sessionEventPanel`)在这里单源,router 与
|
|
7
|
+
* bridge 都只调用不复制。
|
|
8
8
|
*/
|
|
9
|
-
import { markdownDiv } from '
|
|
10
|
-
import type { AgentSessionContentEvent } from '../
|
|
9
|
+
import { markdownDiv } from './collapsible.js';
|
|
10
|
+
import type { AgentSessionContentEvent } from '../session-contract.js';
|
|
11
11
|
import { formatUsageStats } from './usage-format.js';
|
|
12
12
|
type SessionUsageLike = Parameters<typeof formatUsageStats>[0];
|
|
13
13
|
export declare function sessionEventPanel(ev: AgentSessionContentEvent, index: number, totalEvents: number, tailExpandedCount?: number, agentKind?: string): object;
|