lark-remote 0.4.4 → 0.4.5
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 +10 -0
- package/dist/binder.d.ts +11 -10
- package/dist/binder.js +17 -16
- package/dist/bridge/inbound-media.d.ts +1 -27
- package/dist/bridge/inbound-media.js +13 -14
- package/dist/bridge/index.d.ts +49 -49
- package/dist/bridge/index.js +99 -92
- package/dist/bridge/queue-manager.d.ts +33 -26
- package/dist/bridge/queue-manager.js +117 -117
- package/dist/card/bash-renderer.js +4 -4
- package/dist/card/card-shared.d.ts +1 -1
- package/dist/card/card-shared.js +2 -2
- package/dist/card/run-renderer.js +8 -8
- package/dist/cli.js +5 -6
- package/dist/clone.d.ts +1 -6
- package/dist/clone.js +4 -4
- package/dist/common/clamp.d.ts +2 -0
- package/dist/common/clamp.js +5 -0
- package/dist/common/fs.d.ts +4 -3
- package/dist/common/fs.js +5 -4
- package/dist/config/dir.d.ts +4 -5
- package/dist/config/dir.js +7 -8
- package/dist/config/wizard.js +2 -2
- package/dist/connector/index.d.ts +0 -2
- package/dist/connector/index.js +5 -5
- package/dist/inbound/receipt.d.ts +1 -3
- package/dist/inbound/receipt.js +2 -2
- package/dist/inbound/turn-assembler.d.ts +1 -3
- package/dist/inbound/turn-assembler.js +2 -2
- package/dist/index.js +30 -22
- package/dist/instance-lock.js +4 -4
- package/dist/persistence/atomic-write.js +3 -3
- package/dist/platform/agent-stopper.d.ts +1 -1
- package/dist/platform/terminator-posix.js +5 -5
- package/dist/platform/terminator-win32.js +1 -1
- package/dist/router/card-helpers.d.ts +1 -3
- package/dist/router/card-helpers.js +2 -2
- package/dist/router/config/codex.js +1 -1
- package/dist/router/config/common/model-patch.d.ts +1 -2
- package/dist/router/config/common/model-patch.js +1 -2
- package/dist/router/config/opencode.js +1 -1
- package/dist/router/config/pi.js +1 -1
- package/dist/router/index.d.ts +22 -22
- package/dist/router/index.js +71 -52
- package/dist/router/order-exec-dispatch.d.ts +3 -10
- package/dist/router/order-exec-dispatch.js +3 -3
- package/dist/router/{utils.d.ts → usage-format.d.ts} +2 -4
- package/dist/router/{utils.js → usage-format.js} +3 -8
- package/dist/runner/claude/runner.d.ts +1 -1
- package/dist/runner/claude/runner.js +1 -2
- package/dist/runner/claude/session.d.ts +2 -4
- package/dist/runner/claude/session.js +7 -9
- package/dist/runner/common/acp/{protocol-helpers.d.ts → acp-approval.d.ts} +2 -8
- package/dist/runner/common/acp/{protocol-helpers.js → acp-approval.js} +4 -3
- package/dist/runner/common/acp/base-acp-runner.d.ts +1 -1
- package/dist/runner/common/acp/base-acp-runner.js +1 -1
- package/dist/runner/common/connection-based-runner.d.ts +4 -4
- package/dist/runner/common/connection-based-runner.js +3 -3
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +12 -12
- package/dist/runner/common/jsonrpc/connection-manager.js +36 -36
- package/dist/runner/common/{usage.d.ts → result-usage.d.ts} +1 -1
- package/dist/runner/common/{usage.js → result-usage.js} +2 -2
- package/dist/runner/common/spawning-runner.d.ts +2 -2
- package/dist/runner/common/spawning-runner.js +8 -8
- package/dist/runner/common/{runner-utils.d.ts → synthetic-events.d.ts} +4 -4
- package/dist/runner/common/{runner-utils.js → synthetic-events.js} +5 -5
- package/dist/runner/dsh/client.d.ts +1 -1
- package/dist/runner/dsh/client.js +1 -1
- package/dist/runner/dsh/runner.js +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.js +1 -1
- package/dist/runner/kimi/acp/translator.js +1 -1
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +1 -1
- package/dist/runner/opencode/acp/translator.js +1 -1
- package/dist/runner/pi/rpc/runner.d.ts +1 -1
- package/dist/runner/pi/rpc/translator.d.ts +0 -1
- package/dist/runner/pi/rpc/translator.js +0 -2
- package/dist/runner/registry.d.ts +3 -3
- package/dist/runner/registry.js +3 -3
- package/dist/session/claude/session-index.d.ts +1 -1
- package/dist/session/claude/session-index.js +1 -1
- package/dist/session/claude/session-reader.d.ts +1 -1
- package/dist/session/claude/session-reader.js +1 -1
- package/dist/session/codex/rollout-reader.js +2 -2
- package/dist/session/dsh/sessions.d.ts +1 -1
- package/dist/session/dsh/sessions.js +1 -1
- package/dist/session/kimi/sessions.d.ts +0 -4
- package/dist/session/kimi/sessions.js +2 -8
- package/dist/session/opencode/sessions.js +3 -3
- package/dist/session/registry.d.ts +1 -1
- package/dist/session/registry.js +1 -1
- package/dist/session/session-store.d.ts +2 -3
- package/dist/session/session-store.js +2 -4
- package/dist/update/index.d.ts +1 -1
- package/dist/update/index.js +1 -1
- package/dist/update/version-check.d.ts +5 -3
- package/dist/update/version-check.js +10 -8
- package/dist/workspace/index.d.ts +4 -4
- package/dist/workspace/index.js +5 -5
- package/package.json +1 -1
|
@@ -27,7 +27,7 @@ export class QueueManager {
|
|
|
27
27
|
* `updateQueuedTaskMessage`. The ordered array remains the source of truth
|
|
28
28
|
* (position display depends on order); the index must be kept in sync on
|
|
29
29
|
* every mutation (push / splice / shift) via the `indexAdd`/`indexRemove`
|
|
30
|
-
* helpers. Same messageId is never enqueued twice in one
|
|
30
|
+
* helpers. Same messageId is never enqueued twice in one cwd
|
|
31
31
|
* (Feishu dedup + internal-key dedup), so no overwrite concern.
|
|
32
32
|
*/
|
|
33
33
|
taskIndex = new Map();
|
|
@@ -41,7 +41,7 @@ export class QueueManager {
|
|
|
41
41
|
*/
|
|
42
42
|
/** queue card 的 send promise 表(public:bridge 集成测试注入/断言用)。 */
|
|
43
43
|
queueCardMessages = new Map();
|
|
44
|
-
/** Track number of executing tasks per
|
|
44
|
+
/** Track number of executing tasks per cwd (for queue card display). */
|
|
45
45
|
pendingOrExecutingCount = new Map();
|
|
46
46
|
/**
|
|
47
47
|
* Monotonic counter minting a unique slot id per enqueued task. A slot
|
|
@@ -84,7 +84,7 @@ export class QueueManager {
|
|
|
84
84
|
* before begin); `removeFromQueue` therefore needs no cleanup here.
|
|
85
85
|
*/
|
|
86
86
|
beganMessageIds = new Set();
|
|
87
|
-
/** Callback to check if
|
|
87
|
+
/** Callback to check if cwd has an active run */
|
|
88
88
|
isWorkspaceRunning;
|
|
89
89
|
/** Callback to send card updates */
|
|
90
90
|
sendCard;
|
|
@@ -96,30 +96,30 @@ export class QueueManager {
|
|
|
96
96
|
this.updateCard = updateCard;
|
|
97
97
|
}
|
|
98
98
|
/** P3-5: register a queued task in the per-workspace `messageId → task` index. */
|
|
99
|
-
indexAdd(
|
|
100
|
-
let idx = this.taskIndex.get(
|
|
99
|
+
indexAdd(cwd, task) {
|
|
100
|
+
let idx = this.taskIndex.get(cwd);
|
|
101
101
|
if (!idx) {
|
|
102
102
|
idx = new Map();
|
|
103
|
-
this.taskIndex.set(
|
|
103
|
+
this.taskIndex.set(cwd, idx);
|
|
104
104
|
}
|
|
105
105
|
idx.set(task.messageId, task);
|
|
106
106
|
}
|
|
107
107
|
/** P3-5: drop a task from the per-workspace index by messageId. */
|
|
108
|
-
indexRemove(
|
|
109
|
-
this.taskIndex.get(
|
|
108
|
+
indexRemove(cwd, messageId) {
|
|
109
|
+
this.taskIndex.get(cwd)?.delete(messageId);
|
|
110
110
|
}
|
|
111
111
|
/** P3-5: O(1) lookup of a queued task by messageId via the index. */
|
|
112
|
-
indexGet(
|
|
113
|
-
return this.taskIndex.get(
|
|
112
|
+
indexGet(cwd, messageId) {
|
|
113
|
+
return this.taskIndex.get(cwd)?.get(messageId);
|
|
114
114
|
}
|
|
115
115
|
/**
|
|
116
116
|
* Enqueue a task into the workspace-level serial queue.
|
|
117
|
-
* Each
|
|
117
|
+
* Each cwd has its own serial queue for parallel execution across workspaces.
|
|
118
118
|
*/
|
|
119
|
-
enqueue(
|
|
119
|
+
enqueue(cwd, task, opts) {
|
|
120
120
|
// Guard: reject non-function tasks that would poison the queue chain
|
|
121
121
|
if (typeof task !== 'function') {
|
|
122
|
-
getLogger().warn('[queue-manager] enqueue ignored task is not a function,
|
|
122
|
+
getLogger().warn('[queue-manager] enqueue ignored task is not a function, cwd=', cwd);
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
125
|
// Mint a unique slot id for this task's execution period (regardless of
|
|
@@ -137,15 +137,15 @@ export class QueueManager {
|
|
|
137
137
|
chatId: taskMeta.chatId,
|
|
138
138
|
messageId: taskMeta.messageId,
|
|
139
139
|
feishuReplyTo: taskMeta.feishuReplyTo,
|
|
140
|
-
|
|
140
|
+
cwd,
|
|
141
141
|
timestamp: Date.now(),
|
|
142
142
|
messagePreview,
|
|
143
143
|
editable: taskMeta.editable,
|
|
144
144
|
binding: taskMeta.binding,
|
|
145
145
|
};
|
|
146
146
|
// Check if there are tasks waiting BEFORE adding this one
|
|
147
|
-
const currentExecutingCount = this.pendingOrExecutingCount.get(
|
|
148
|
-
const currentQueueLength = this.queuedTasks.get(
|
|
147
|
+
const currentExecutingCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
148
|
+
const currentQueueLength = this.queuedTasks.get(cwd)?.length ?? 0;
|
|
149
149
|
const hasWaitingTasks = currentExecutingCount > 0 || currentQueueLength > 0;
|
|
150
150
|
// P2-5: bound the waiting backlog. When the queue is full, reject the
|
|
151
151
|
// task with a visible card instead of appending it (which would grow
|
|
@@ -153,7 +153,7 @@ export class QueueManager {
|
|
|
153
153
|
// fire another queue card). Do NOT increment pendingOrExecutingCount —
|
|
154
154
|
// a rejected task never executes, so it must not occupy a slot.
|
|
155
155
|
if (currentQueueLength >= MAX_WAITING_QUEUE) {
|
|
156
|
-
getLogger().warn(`[queue-manager] queue full
|
|
156
|
+
getLogger().warn(`[queue-manager] queue full cwd=${cwd} depth=${currentQueueLength} rejecting task messageId=${taskMeta.messageId}`);
|
|
157
157
|
void this.sendCard(taskMeta.chatId, {
|
|
158
158
|
schema: '2.0',
|
|
159
159
|
config: { wide_screen_mode: true },
|
|
@@ -167,7 +167,7 @@ export class QueueManager {
|
|
|
167
167
|
tag: 'div',
|
|
168
168
|
text: {
|
|
169
169
|
tag: 'lark_md',
|
|
170
|
-
content:
|
|
170
|
+
content: `当前工作目录已有 ${MAX_WAITING_QUEUE} 条消息排队等待,为防止积压已拒收本条消息。请等待队列消化后重发,或用 \`/stop\` 清空当前任务。`,
|
|
171
171
|
},
|
|
172
172
|
},
|
|
173
173
|
],
|
|
@@ -177,38 +177,38 @@ export class QueueManager {
|
|
|
177
177
|
});
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
|
-
// Add to
|
|
181
|
-
let taskList = this.queuedTasks.get(
|
|
180
|
+
// Add to cwd queue list
|
|
181
|
+
let taskList = this.queuedTasks.get(cwd);
|
|
182
182
|
if (!taskList) {
|
|
183
183
|
taskList = [];
|
|
184
|
-
this.queuedTasks.set(
|
|
184
|
+
this.queuedTasks.set(cwd, taskList);
|
|
185
185
|
}
|
|
186
186
|
taskList.push(queuedTask);
|
|
187
|
-
this.indexAdd(
|
|
187
|
+
this.indexAdd(cwd, queuedTask);
|
|
188
188
|
// Increment executing count SYNCHRONOUSLY so subsequent enqueues see it
|
|
189
|
-
this.pendingOrExecutingCount.set(
|
|
189
|
+
this.pendingOrExecutingCount.set(cwd, currentExecutingCount + 1);
|
|
190
190
|
// Only send the queue card if the task actually has to wait.
|
|
191
191
|
if (hasWaitingTasks) {
|
|
192
|
-
void this.sendQueueStatusCard(
|
|
192
|
+
void this.sendQueueStatusCard(cwd, taskMeta.chatId, taskMeta.messageId, messagePreview,
|
|
193
193
|
// Reply to the real Feishu message id when the queue key is an
|
|
194
194
|
// internal key (order.exec); otherwise messageId IS the Feishu id.
|
|
195
195
|
taskMeta.feishuReplyTo);
|
|
196
196
|
}
|
|
197
|
-
getLogger().debug(`[queue-manager] enqueue task queued
|
|
197
|
+
getLogger().debug(`[queue-manager] enqueue task queued cwd=${cwd} queueCard=${hasWaitingTasks} executing=${currentExecutingCount + 1} queueLen=${currentQueueLength + 1}`);
|
|
198
198
|
}
|
|
199
|
-
// Get or create the queue for this
|
|
200
|
-
let queue = this.queues.get(
|
|
199
|
+
// Get or create the queue for this cwd
|
|
200
|
+
let queue = this.queues.get(cwd);
|
|
201
201
|
if (!queue) {
|
|
202
202
|
queue = Promise.resolve();
|
|
203
|
-
this.queues.set(
|
|
203
|
+
this.queues.set(cwd, queue);
|
|
204
204
|
}
|
|
205
|
-
getLogger().debug(`[queue-manager] enqueue
|
|
205
|
+
getLogger().debug(`[queue-manager] enqueue cwd=${cwd}`);
|
|
206
206
|
// Capture messageId for cancellation guard
|
|
207
207
|
const messageId = taskMeta?.messageId;
|
|
208
208
|
// messagePreview already captured above (outside taskMeta block)
|
|
209
209
|
const newQueue = queue
|
|
210
210
|
.then(() => {
|
|
211
|
-
getLogger().debug(`[queue-manager] task begin
|
|
211
|
+
getLogger().debug(`[queue-manager] task begin cwd=${cwd} messageId=${messageId}`);
|
|
212
212
|
// Live preview for the executing card: read from the QueuedTask at
|
|
213
213
|
// begin time, not the enqueue-closure `taskMeta` (which is frozen).
|
|
214
214
|
// `updateQueuedTaskMessage` (queue.edit/queue.input) mutates the live
|
|
@@ -221,19 +221,19 @@ export class QueueManager {
|
|
|
221
221
|
// double O(N) scan. The ordered array still drives removal (splice
|
|
222
222
|
// preserves queue order for position display); the index stays in
|
|
223
223
|
// sync via indexRemove.
|
|
224
|
-
const task = this.indexGet(
|
|
224
|
+
const task = this.indexGet(cwd, messageId);
|
|
225
225
|
if (!task) {
|
|
226
|
-
getLogger().debug(`[queue-manager] task skipped (cancelled)
|
|
226
|
+
getLogger().debug(`[queue-manager] task skipped (cancelled) cwd=${cwd} messageId=${messageId}`);
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
229
|
livePreview = task.messagePreview;
|
|
230
230
|
// Remove this task's metadata by messageId
|
|
231
|
-
const taskList = this.queuedTasks.get(
|
|
231
|
+
const taskList = this.queuedTasks.get(cwd);
|
|
232
232
|
const idx = taskList?.findIndex((t) => t.messageId === messageId) ?? -1;
|
|
233
233
|
if (taskList && idx >= 0) {
|
|
234
234
|
taskList.splice(idx, 1);
|
|
235
235
|
}
|
|
236
|
-
this.indexRemove(
|
|
236
|
+
this.indexRemove(cwd, messageId);
|
|
237
237
|
// Cancellation check passed and the task has been removed: it is
|
|
238
238
|
// about to run, so record it as began for queue.immediate feedback.
|
|
239
239
|
// Bounded: ids are only consulted for the current immediate target,
|
|
@@ -252,12 +252,12 @@ export class QueueManager {
|
|
|
252
252
|
// so a stale closure can never run on a later task.
|
|
253
253
|
let replacement;
|
|
254
254
|
if (messageId) {
|
|
255
|
-
const workspaceReplacements = this.taskReplacements.get(
|
|
255
|
+
const workspaceReplacements = this.taskReplacements.get(cwd);
|
|
256
256
|
replacement = workspaceReplacements?.get(messageId);
|
|
257
257
|
if (replacement) {
|
|
258
258
|
workspaceReplacements?.delete(messageId);
|
|
259
259
|
if (workspaceReplacements && workspaceReplacements.size === 0) {
|
|
260
|
-
this.taskReplacements.delete(
|
|
260
|
+
this.taskReplacements.delete(cwd);
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
}
|
|
@@ -265,7 +265,7 @@ export class QueueManager {
|
|
|
265
265
|
// This must happen after the cancellation check: a skipped/cancelled
|
|
266
266
|
// task must keep its "❌ 已撤销" card, not be flipped to executing.
|
|
267
267
|
if (messageId) {
|
|
268
|
-
void this.updateQueueCardToExecuting(
|
|
268
|
+
void this.updateQueueCardToExecuting(cwd, messageId, livePreview, true);
|
|
269
269
|
}
|
|
270
270
|
// Re-arm the pending/executing count for every task that is about to
|
|
271
271
|
// run. `resetExecutingCount` (external interrupt) zeroes the count,
|
|
@@ -273,42 +273,42 @@ export class QueueManager {
|
|
|
273
273
|
// task enqueued BEFORE the interrupt can begin with count 0. Without
|
|
274
274
|
// this, a running task is invisible to later enqueues and they
|
|
275
275
|
// silently skip the "⏳ 消息排队中" card. This applies regardless of
|
|
276
|
-
// taskMeta: resetExecutingCount can clear the count for any
|
|
276
|
+
// taskMeta: resetExecutingCount can clear the count for any cwd,
|
|
277
277
|
// and a task that resumes after an interrupt must re-arm even when it
|
|
278
278
|
// carries no metadata.
|
|
279
|
-
const currentCount = this.pendingOrExecutingCount.get(
|
|
279
|
+
const currentCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
280
280
|
if (currentCount < 1) {
|
|
281
|
-
this.pendingOrExecutingCount.set(
|
|
282
|
-
getLogger().debug(`[queue-manager] re-armed pendingOrExecutingCount
|
|
281
|
+
this.pendingOrExecutingCount.set(cwd, 1);
|
|
282
|
+
getLogger().debug(`[queue-manager] re-armed pendingOrExecutingCount cwd=${cwd} (interrupt resume)`);
|
|
283
283
|
}
|
|
284
284
|
// Mark this task's slot as the current execution period before running
|
|
285
285
|
// it. `resetExecutingCount` reads this marker to grant the interrupt
|
|
286
286
|
// credit to exactly this task; the settle removes the marker.
|
|
287
|
-
this.executingSlot.set(
|
|
287
|
+
this.executingSlot.set(cwd, slotId);
|
|
288
288
|
return replacement ? replacement() : task();
|
|
289
289
|
})
|
|
290
290
|
.then(() => {
|
|
291
|
-
getLogger().debug(`[queue-manager] task end
|
|
291
|
+
getLogger().debug(`[queue-manager] task end cwd=${cwd}`);
|
|
292
292
|
// beganMessageIds entry intentionally retained: sticky by design
|
|
293
293
|
// (A21) — the marker records "has ever begun", not "is currently
|
|
294
294
|
// running", so a task that began and settled quickly still reports
|
|
295
295
|
// "已开始执行" to queue.immediate.
|
|
296
|
-
this.decrementExecutingCount(
|
|
296
|
+
this.decrementExecutingCount(cwd, slotId);
|
|
297
297
|
})
|
|
298
298
|
.catch((err) => {
|
|
299
299
|
getLogger().error('[queue-manager] queue task error:', err);
|
|
300
300
|
// Same sticky rationale as the success settle: the marker survives
|
|
301
301
|
// even when the task errors after beginning (A21).
|
|
302
|
-
this.decrementExecutingCount(
|
|
302
|
+
this.decrementExecutingCount(cwd, slotId);
|
|
303
303
|
});
|
|
304
|
-
this.queues.set(
|
|
304
|
+
this.queues.set(cwd, newQueue);
|
|
305
305
|
}
|
|
306
306
|
/**
|
|
307
307
|
* Execute a task immediately without going through the queue.
|
|
308
308
|
* Used for / commands that should respond immediately.
|
|
309
309
|
*/
|
|
310
|
-
enqueueImmediate(
|
|
311
|
-
getLogger().debug(`[queue-manager] enqueueImmediate
|
|
310
|
+
enqueueImmediate(cwd, task) {
|
|
311
|
+
getLogger().debug(`[queue-manager] enqueueImmediate cwd=${cwd}`);
|
|
312
312
|
void task().catch((err) => getLogger().error('[queue-manager] immediate task error:', err));
|
|
313
313
|
}
|
|
314
314
|
/**
|
|
@@ -316,22 +316,22 @@ export class QueueManager {
|
|
|
316
316
|
* place, preserving its queue position (tasks queued behind it still run
|
|
317
317
|
* after it). Consumed when the task's slot begins; removed if cancelled.
|
|
318
318
|
*/
|
|
319
|
-
setTaskReplacement(
|
|
320
|
-
let workspaceReplacements = this.taskReplacements.get(
|
|
319
|
+
setTaskReplacement(cwd, messageId, task) {
|
|
320
|
+
let workspaceReplacements = this.taskReplacements.get(cwd);
|
|
321
321
|
if (!workspaceReplacements) {
|
|
322
322
|
workspaceReplacements = new Map();
|
|
323
|
-
this.taskReplacements.set(
|
|
323
|
+
this.taskReplacements.set(cwd, workspaceReplacements);
|
|
324
324
|
}
|
|
325
325
|
workspaceReplacements.set(messageId, task);
|
|
326
|
-
getLogger().debug(`[queue-manager] set task replacement
|
|
326
|
+
getLogger().debug(`[queue-manager] set task replacement cwd=${cwd} messageId=${messageId}`);
|
|
327
327
|
}
|
|
328
328
|
/** Send a queue status card showing current queue position and actions. */
|
|
329
|
-
async sendQueueStatusCard(
|
|
329
|
+
async sendQueueStatusCard(cwd, chatId, replyToMessageId, messagePreview,
|
|
330
330
|
/** Feishu reply target when `replyToMessageId` (the queue key) is an
|
|
331
331
|
* internal key, not a valid Feishu message id. Defaults to the queue key
|
|
332
332
|
* for hand-typed messages. */
|
|
333
333
|
feishuReplyTo) {
|
|
334
|
-
const taskList = this.queuedTasks.get(
|
|
334
|
+
const taskList = this.queuedTasks.get(cwd) ?? [];
|
|
335
335
|
const positionInQueue = taskList.findIndex((t) => t.chatId === chatId && t.messageId === replyToMessageId);
|
|
336
336
|
const actualPosition = positionInQueue + 1;
|
|
337
337
|
const tasksAhead = actualPosition - 1;
|
|
@@ -343,7 +343,7 @@ export class QueueManager {
|
|
|
343
343
|
title: { tag: 'plain_text', content: '⏳ 消息排队中' },
|
|
344
344
|
},
|
|
345
345
|
body: {
|
|
346
|
-
elements: this.buildQueueStatusCardElements(
|
|
346
|
+
elements: this.buildQueueStatusCardElements(cwd, actualPosition, tasksAhead, replyToMessageId, messagePreview),
|
|
347
347
|
},
|
|
348
348
|
};
|
|
349
349
|
// The Feishu reply target must be a real Feishu message id. The queue
|
|
@@ -367,19 +367,19 @@ export class QueueManager {
|
|
|
367
367
|
}
|
|
368
368
|
return messageId;
|
|
369
369
|
}
|
|
370
|
-
/** Get queue info for a
|
|
371
|
-
getQueueInfo(
|
|
372
|
-
const taskList = this.queuedTasks.get(
|
|
370
|
+
/** Get queue info for a cwd. */
|
|
371
|
+
getQueueInfo(cwd) {
|
|
372
|
+
const taskList = this.queuedTasks.get(cwd) ?? [];
|
|
373
373
|
return {
|
|
374
374
|
position: taskList.length,
|
|
375
375
|
tasksAhead: Math.max(0, taskList.length - 1),
|
|
376
|
-
isRunning: this.isWorkspaceRunning(
|
|
376
|
+
isRunning: this.isWorkspaceRunning(cwd),
|
|
377
377
|
};
|
|
378
378
|
}
|
|
379
379
|
/** Build queue status card elements. */
|
|
380
|
-
buildQueueStatusCardElements(
|
|
381
|
-
const workspaceName = displayName(
|
|
382
|
-
const isRunning = this.isWorkspaceRunning(
|
|
380
|
+
buildQueueStatusCardElements(cwd, actualPosition, tasksAhead, messageId, messagePreview) {
|
|
381
|
+
const workspaceName = displayName(cwd);
|
|
382
|
+
const isRunning = this.isWorkspaceRunning(cwd);
|
|
383
383
|
const elements = [
|
|
384
384
|
{
|
|
385
385
|
tag: 'div',
|
|
@@ -408,7 +408,7 @@ export class QueueManager {
|
|
|
408
408
|
tag: 'div',
|
|
409
409
|
text: { tag: 'lark_md', content: `📝 \`${messagePreview}\`` },
|
|
410
410
|
});
|
|
411
|
-
const task = this.indexGet(
|
|
411
|
+
const task = this.indexGet(cwd, messageId);
|
|
412
412
|
if (task?.editable !== false) {
|
|
413
413
|
// Edit button: a pending (queued) task is editable by default. The
|
|
414
414
|
// executing/cancelled states render via dedicated card builders that
|
|
@@ -418,40 +418,40 @@ export class QueueManager {
|
|
|
418
418
|
text: { tag: 'plain_text', content: '✏️ 编辑' },
|
|
419
419
|
type: 'default',
|
|
420
420
|
size: 'small',
|
|
421
|
-
behaviors: [{ type: 'callback', value: { cmd: 'queue.edit',
|
|
421
|
+
behaviors: [{ type: 'callback', value: { cmd: 'queue.edit', cwd, messageId } }],
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
elements.push({ tag: 'hr' });
|
|
425
425
|
}
|
|
426
426
|
// Action buttons
|
|
427
|
-
elements.push(...this.buildQueueActionButtons(
|
|
427
|
+
elements.push(...this.buildQueueActionButtons(cwd, messageId, false));
|
|
428
428
|
return elements;
|
|
429
429
|
}
|
|
430
430
|
/**
|
|
431
431
|
* Build the 撤销/立即执行 action button pair for a queue card.
|
|
432
432
|
*
|
|
433
433
|
* Both buttons route to `queue.cancel` / `queue.immediate` callbacks with
|
|
434
|
-
* the same
|
|
434
|
+
* the same cwd/messageId; only the `disabled` flag varies by card
|
|
435
435
|
* state — pending cards enable both, executing/cancelled cards disable
|
|
436
436
|
* both. Centralizing the pair here eliminates the 3-way duplication
|
|
437
437
|
* between `buildQueueStatusCardElements`, `updateQueueCardToExecuting`,
|
|
438
438
|
* and `updateQueueCardToCancelled` (Clean Code P2-1).
|
|
439
439
|
*/
|
|
440
|
-
buildQueueActionButtons(
|
|
440
|
+
buildQueueActionButtons(cwd, messageId, disabled) {
|
|
441
441
|
return [
|
|
442
442
|
{
|
|
443
443
|
tag: 'button',
|
|
444
444
|
text: { tag: 'plain_text', content: '❌ 撤销' },
|
|
445
445
|
type: 'danger',
|
|
446
446
|
disabled,
|
|
447
|
-
behaviors: [{ type: 'callback', value: { cmd: 'queue.cancel',
|
|
447
|
+
behaviors: [{ type: 'callback', value: { cmd: 'queue.cancel', cwd, messageId } }],
|
|
448
448
|
},
|
|
449
449
|
{
|
|
450
450
|
tag: 'button',
|
|
451
451
|
text: { tag: 'plain_text', content: '⚡ 立即执行' },
|
|
452
452
|
type: 'primary',
|
|
453
453
|
disabled,
|
|
454
|
-
behaviors: [{ type: 'callback', value: { cmd: 'queue.immediate',
|
|
454
|
+
behaviors: [{ type: 'callback', value: { cmd: 'queue.immediate', cwd, messageId } }],
|
|
455
455
|
},
|
|
456
456
|
];
|
|
457
457
|
}
|
|
@@ -466,20 +466,20 @@ export class QueueManager {
|
|
|
466
466
|
* the card must keep its "❌ 已撤销" state instead of being flipped back to
|
|
467
467
|
* executing. The mapping is deleted in finally in both cases.
|
|
468
468
|
*/
|
|
469
|
-
async updateQueueCardToExecuting(
|
|
469
|
+
async updateQueueCardToExecuting(cwd, messageId, messagePreview, started = false) {
|
|
470
470
|
const cardMessageId = await this.queueCardMessages.get(messageId);
|
|
471
471
|
if (!cardMessageId) {
|
|
472
472
|
getLogger().debug(`[queue-manager] no queue card to update, messageId=${messageId}`);
|
|
473
473
|
return;
|
|
474
474
|
}
|
|
475
|
-
const workspaceName = displayName(
|
|
475
|
+
const workspaceName = displayName(cwd);
|
|
476
476
|
try {
|
|
477
477
|
// Prefer the live preview: an edited task's messagePreview is updated in
|
|
478
478
|
// place, and the passed-in snapshot may be stale if the card send was in
|
|
479
479
|
// flight while the user edited (A19). The begin path (started=true) already
|
|
480
480
|
// passes the live preview captured at begin (A12), so the fallback is only
|
|
481
481
|
// exercised when the task is no longer queued.
|
|
482
|
-
const liveTask = this.indexGet(
|
|
482
|
+
const liveTask = this.indexGet(cwd, messageId);
|
|
483
483
|
const stillQueued = liveTask !== undefined;
|
|
484
484
|
if (!stillQueued && !started) {
|
|
485
485
|
getLogger().debug(`[queue-manager] queue card update to executing skipped (task no longer queued and not started) messageId=${messageId}`);
|
|
@@ -502,7 +502,7 @@ export class QueueManager {
|
|
|
502
502
|
{ tag: 'hr' },
|
|
503
503
|
{ tag: 'div', text: { tag: 'lark_md', content: `📝 \`${previewForCard}\`` } },
|
|
504
504
|
{ tag: 'hr' },
|
|
505
|
-
...this.buildQueueActionButtons(
|
|
505
|
+
...this.buildQueueActionButtons(cwd, messageId, true),
|
|
506
506
|
],
|
|
507
507
|
},
|
|
508
508
|
};
|
|
@@ -517,38 +517,38 @@ export class QueueManager {
|
|
|
517
517
|
}
|
|
518
518
|
}
|
|
519
519
|
/** Remove a task from the queue by messageId. Returns true if found and removed. */
|
|
520
|
-
removeFromQueue(
|
|
520
|
+
removeFromQueue(cwd, messageId) {
|
|
521
521
|
// Drop any one-shot replacement for this task: a cancelled/removed task
|
|
522
522
|
// must never leave a closure behind that could execute later.
|
|
523
|
-
const workspaceReplacements = this.taskReplacements.get(
|
|
523
|
+
const workspaceReplacements = this.taskReplacements.get(cwd);
|
|
524
524
|
if (workspaceReplacements?.delete(messageId) && workspaceReplacements.size === 0) {
|
|
525
|
-
this.taskReplacements.delete(
|
|
525
|
+
this.taskReplacements.delete(cwd);
|
|
526
526
|
}
|
|
527
|
-
const taskList = this.queuedTasks.get(
|
|
527
|
+
const taskList = this.queuedTasks.get(cwd);
|
|
528
528
|
if (!taskList)
|
|
529
529
|
return false;
|
|
530
530
|
// P3-5: index is the O(1) presence check; array splice keeps order.
|
|
531
|
-
if (!this.indexGet(
|
|
531
|
+
if (!this.indexGet(cwd, messageId))
|
|
532
532
|
return false;
|
|
533
533
|
const index = taskList.findIndex((t) => t.messageId === messageId);
|
|
534
534
|
if (index >= 0) {
|
|
535
535
|
taskList.splice(index, 1);
|
|
536
536
|
}
|
|
537
|
-
this.indexRemove(
|
|
537
|
+
this.indexRemove(cwd, messageId);
|
|
538
538
|
// Note: queueCardMessages mapping is NOT deleted here.
|
|
539
539
|
// The caller (handleQueueCancel) will call updateQueueCardToCancelled
|
|
540
540
|
// which will clean up the mapping after updating the card.
|
|
541
|
-
getLogger().info(`[queue-manager] removed from queue
|
|
541
|
+
getLogger().info(`[queue-manager] removed from queue cwd=${cwd} messageId=${messageId}`);
|
|
542
542
|
return true;
|
|
543
543
|
}
|
|
544
544
|
/** Update queue card to "cancelled" status when user clicks 撤销. */
|
|
545
|
-
async updateQueueCardToCancelled(
|
|
545
|
+
async updateQueueCardToCancelled(cwd, messageId) {
|
|
546
546
|
const cardMessageId = await this.queueCardMessages.get(messageId);
|
|
547
547
|
if (!cardMessageId) {
|
|
548
548
|
getLogger().debug(`[queue-manager] no queue card to update (cancelled), messageId=${messageId}`);
|
|
549
549
|
return;
|
|
550
550
|
}
|
|
551
|
-
const workspaceName = displayName(
|
|
551
|
+
const workspaceName = displayName(cwd);
|
|
552
552
|
const card = {
|
|
553
553
|
schema: '2.0',
|
|
554
554
|
config: { wide_screen_mode: true },
|
|
@@ -565,7 +565,7 @@ export class QueueManager {
|
|
|
565
565
|
{ tag: 'hr' },
|
|
566
566
|
{ tag: 'div', text: { tag: 'lark_md', content: `📝 该消息已从队列中撤销` } },
|
|
567
567
|
{ tag: 'hr' },
|
|
568
|
-
...this.buildQueueActionButtons(
|
|
568
|
+
...this.buildQueueActionButtons(cwd, messageId, true),
|
|
569
569
|
],
|
|
570
570
|
},
|
|
571
571
|
};
|
|
@@ -581,9 +581,9 @@ export class QueueManager {
|
|
|
581
581
|
}
|
|
582
582
|
}
|
|
583
583
|
/** Get task metadata from queue. */
|
|
584
|
-
getQueuedTask(
|
|
584
|
+
getQueuedTask(cwd, messageId) {
|
|
585
585
|
// P3-5: O(1) index lookup instead of array `find`.
|
|
586
|
-
return this.indexGet(
|
|
586
|
+
return this.indexGet(cwd, messageId);
|
|
587
587
|
}
|
|
588
588
|
/**
|
|
589
589
|
* Whether a task with the given messageId has begun executing (cancellation
|
|
@@ -594,19 +594,19 @@ export class QueueManager {
|
|
|
594
594
|
hasBegan(messageId) {
|
|
595
595
|
return this.beganMessageIds.has(messageId);
|
|
596
596
|
}
|
|
597
|
-
/** Get all queued tasks for a
|
|
598
|
-
getQueuedTasks(
|
|
599
|
-
return [...(this.queuedTasks.get(
|
|
597
|
+
/** Get all queued tasks for a cwd. Returns a copy to prevent aliasing bugs. */
|
|
598
|
+
getQueuedTasks(cwd) {
|
|
599
|
+
return [...(this.queuedTasks.get(cwd) ?? [])];
|
|
600
600
|
}
|
|
601
601
|
/** Update the messagePreview for a queued task. Returns true if found and updated. */
|
|
602
|
-
updateQueuedTaskMessage(
|
|
602
|
+
updateQueuedTaskMessage(cwd, messageId, newMessage) {
|
|
603
603
|
// P3-5: O(1) index lookup instead of array `find`.
|
|
604
|
-
const task = this.indexGet(
|
|
604
|
+
const task = this.indexGet(cwd, messageId);
|
|
605
605
|
if (!task)
|
|
606
606
|
return false;
|
|
607
607
|
task.messagePreview = newMessage;
|
|
608
608
|
task.editedMessage = newMessage;
|
|
609
|
-
getLogger().info(`[queue-manager] updated messagePreview
|
|
609
|
+
getLogger().info(`[queue-manager] updated messagePreview cwd=${cwd} messageId=${messageId}`);
|
|
610
610
|
return true;
|
|
611
611
|
}
|
|
612
612
|
/**
|
|
@@ -621,8 +621,8 @@ export class QueueManager {
|
|
|
621
621
|
* concurrent PATCH updateCard result. Returning { card } in the callback
|
|
622
622
|
* response updates the card synchronously with no API race.
|
|
623
623
|
*/
|
|
624
|
-
buildQueueCardForEdit(
|
|
625
|
-
const taskList = this.queuedTasks.get(
|
|
624
|
+
buildQueueCardForEdit(cwd, messageId, newMessagePreview) {
|
|
625
|
+
const taskList = this.queuedTasks.get(cwd) ?? [];
|
|
626
626
|
const positionInQueue = taskList.findIndex((t) => t.messageId === messageId);
|
|
627
627
|
if (positionInQueue < 0) {
|
|
628
628
|
getLogger().info(`[queue-manager] task not in queue (build edit card), messageId=${messageId}`);
|
|
@@ -638,12 +638,12 @@ export class QueueManager {
|
|
|
638
638
|
title: { tag: 'plain_text', content: '⏳ 消息排队中' },
|
|
639
639
|
},
|
|
640
640
|
body: {
|
|
641
|
-
elements: this.buildQueueStatusCardElements(
|
|
641
|
+
elements: this.buildQueueStatusCardElements(cwd, actualPosition, tasksAhead, messageId, newMessagePreview),
|
|
642
642
|
},
|
|
643
643
|
};
|
|
644
644
|
}
|
|
645
645
|
/**
|
|
646
|
-
* Decrement `pendingOrExecutingCount` for a
|
|
646
|
+
* Decrement `pendingOrExecutingCount` for a cwd, honoring per-slot
|
|
647
647
|
* interrupt bookkeeping.
|
|
648
648
|
*
|
|
649
649
|
* Called from the queue chain's `.then()`/`.catch()` settle with the slot
|
|
@@ -657,24 +657,24 @@ export class QueueManager {
|
|
|
657
657
|
* newer task (enqueued after the reset) is still running.
|
|
658
658
|
* 3. Otherwise → normal decrement.
|
|
659
659
|
*/
|
|
660
|
-
decrementExecutingCount(
|
|
661
|
-
if (this.executingSlot.get(
|
|
662
|
-
this.executingSlot.delete(
|
|
660
|
+
decrementExecutingCount(cwd, slotId) {
|
|
661
|
+
if (this.executingSlot.get(cwd) === slotId) {
|
|
662
|
+
this.executingSlot.delete(cwd);
|
|
663
663
|
}
|
|
664
|
-
const interrupted = this.interruptedSlots.get(
|
|
664
|
+
const interrupted = this.interruptedSlots.get(cwd);
|
|
665
665
|
if (interrupted?.has(slotId)) {
|
|
666
666
|
interrupted.delete(slotId);
|
|
667
667
|
if (interrupted.size === 0) {
|
|
668
|
-
this.interruptedSlots.delete(
|
|
668
|
+
this.interruptedSlots.delete(cwd);
|
|
669
669
|
}
|
|
670
|
-
getLogger().debug(`[queue-manager] skip decrement (interrupted slot)
|
|
670
|
+
getLogger().debug(`[queue-manager] skip decrement (interrupted slot) cwd=${cwd} slot=${slotId}`);
|
|
671
671
|
return;
|
|
672
672
|
}
|
|
673
|
-
const count = this.pendingOrExecutingCount.get(
|
|
674
|
-
this.pendingOrExecutingCount.set(
|
|
673
|
+
const count = this.pendingOrExecutingCount.get(cwd) ?? 1;
|
|
674
|
+
this.pendingOrExecutingCount.set(cwd, Math.max(0, count - 1));
|
|
675
675
|
}
|
|
676
676
|
/**
|
|
677
|
-
* Reset the executing task count for a
|
|
677
|
+
* Reset the executing task count for a cwd.
|
|
678
678
|
*
|
|
679
679
|
* This should be called when a running task is interrupted externally
|
|
680
680
|
* (e.g., via /stop command or "立即执行" button) so that the queue
|
|
@@ -699,41 +699,41 @@ export class QueueManager {
|
|
|
699
699
|
* running successor and mark ITS slot interrupted, hiding it from the
|
|
700
700
|
* queue card (A22).
|
|
701
701
|
*/
|
|
702
|
-
resetExecutingCount(
|
|
703
|
-
const currentSlot = this.executingSlot.get(
|
|
702
|
+
resetExecutingCount(cwd, expectedSlot) {
|
|
703
|
+
const currentSlot = this.executingSlot.get(cwd);
|
|
704
704
|
if (currentSlot !== expectedSlot) {
|
|
705
|
-
getLogger().debug(`[queue-manager] resetExecutingCount skip (stopped task settled, slot advanced)
|
|
705
|
+
getLogger().debug(`[queue-manager] resetExecutingCount skip (stopped task settled, slot advanced) cwd=${cwd} expectedSlot=${expectedSlot} currentSlot=${currentSlot ?? 'none'}`);
|
|
706
706
|
return;
|
|
707
707
|
}
|
|
708
708
|
// P3#9: only reset and grant an interrupt slot when there is actually a
|
|
709
709
|
// pending/executing task.
|
|
710
|
-
const currentCount = this.pendingOrExecutingCount.get(
|
|
710
|
+
const currentCount = this.pendingOrExecutingCount.get(cwd) ?? 0;
|
|
711
711
|
if (currentCount === 0) {
|
|
712
|
-
getLogger().debug(`[queue-manager] resetExecutingCount no-op (count already 0)
|
|
712
|
+
getLogger().debug(`[queue-manager] resetExecutingCount no-op (count already 0) cwd=${cwd}`);
|
|
713
713
|
return;
|
|
714
714
|
}
|
|
715
715
|
// Bind the interrupt credit to the currently executing task's slot (if
|
|
716
716
|
// any; defensive against a count without an executing task). `Set.add`
|
|
717
717
|
// dedupes repeated resets of the same slot.
|
|
718
718
|
const slot = currentSlot;
|
|
719
|
-
this.pendingOrExecutingCount.set(
|
|
719
|
+
this.pendingOrExecutingCount.set(cwd, 0);
|
|
720
720
|
if (slot !== undefined) {
|
|
721
|
-
let interrupted = this.interruptedSlots.get(
|
|
721
|
+
let interrupted = this.interruptedSlots.get(cwd);
|
|
722
722
|
if (!interrupted) {
|
|
723
723
|
interrupted = new Set();
|
|
724
|
-
this.interruptedSlots.set(
|
|
724
|
+
this.interruptedSlots.set(cwd, interrupted);
|
|
725
725
|
}
|
|
726
726
|
interrupted.add(slot);
|
|
727
|
-
getLogger().debug(`[queue-manager] reset pendingOrExecutingCount
|
|
727
|
+
getLogger().debug(`[queue-manager] reset pendingOrExecutingCount cwd=${cwd} interruptedSlot=${slot}`);
|
|
728
728
|
return;
|
|
729
729
|
}
|
|
730
|
-
getLogger().debug(`[queue-manager] reset pendingOrExecutingCount
|
|
730
|
+
getLogger().debug(`[queue-manager] reset pendingOrExecutingCount cwd=${cwd} (no executing slot)`);
|
|
731
731
|
}
|
|
732
732
|
/**
|
|
733
|
-
* The slot id of the task currently executing in this
|
|
733
|
+
* The slot id of the task currently executing in this cwd (if any).
|
|
734
734
|
*/
|
|
735
|
-
getExecutingSlot(
|
|
736
|
-
return this.executingSlot.get(
|
|
735
|
+
getExecutingSlot(cwd) {
|
|
736
|
+
return this.executingSlot.get(cwd);
|
|
737
737
|
}
|
|
738
738
|
}
|
|
739
739
|
//# sourceMappingURL=queue-manager.js.map
|
|
@@ -71,8 +71,8 @@ function assembleCard(state, elements) {
|
|
|
71
71
|
schema: '2.0',
|
|
72
72
|
config: { wide_screen_mode: true, update_multi: true },
|
|
73
73
|
header: {
|
|
74
|
-
template:
|
|
75
|
-
title: { content:
|
|
74
|
+
template: bashHeaderTemplate(state.terminal),
|
|
75
|
+
title: { content: bashHeaderTitle(state.terminal), tag: 'plain_text' },
|
|
76
76
|
},
|
|
77
77
|
body: { elements },
|
|
78
78
|
};
|
|
@@ -161,7 +161,7 @@ function bashStatusTagLabel(terminal, exitCode) {
|
|
|
161
161
|
return '出错';
|
|
162
162
|
}
|
|
163
163
|
/** CardKit 2.0 header title */
|
|
164
|
-
function
|
|
164
|
+
function bashHeaderTitle(terminal) {
|
|
165
165
|
if (terminal === 'running')
|
|
166
166
|
return '💻 执行命令...';
|
|
167
167
|
if (terminal === 'error')
|
|
@@ -171,7 +171,7 @@ function bashHeaderTitle2(terminal) {
|
|
|
171
171
|
return '✅ 命令执行完成';
|
|
172
172
|
}
|
|
173
173
|
/** CardKit 2.0 header template */
|
|
174
|
-
function
|
|
174
|
+
function bashHeaderTemplate(terminal) {
|
|
175
175
|
return terminalToColor(terminal);
|
|
176
176
|
}
|
|
177
177
|
//# sourceMappingURL=bash-renderer.js.map
|