@livekit/agents 1.0.1 → 1.0.3
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/dist/ipc/job_proc_lazy_main.cjs +4 -4
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/job_proc_lazy_main.js +6 -6
- package/dist/ipc/job_proc_lazy_main.js.map +1 -1
- package/dist/ipc/proc_pool.cjs +14 -2
- package/dist/ipc/proc_pool.cjs.map +1 -1
- package/dist/ipc/proc_pool.d.ts.map +1 -1
- package/dist/ipc/proc_pool.js +14 -2
- package/dist/ipc/proc_pool.js.map +1 -1
- package/dist/ipc/supervised_proc.cjs +32 -10
- package/dist/ipc/supervised_proc.cjs.map +1 -1
- package/dist/ipc/supervised_proc.d.cts +2 -0
- package/dist/ipc/supervised_proc.d.ts +2 -0
- package/dist/ipc/supervised_proc.d.ts.map +1 -1
- package/dist/ipc/supervised_proc.js +22 -10
- package/dist/ipc/supervised_proc.js.map +1 -1
- package/dist/job.cjs +20 -14
- package/dist/job.cjs.map +1 -1
- package/dist/job.d.cts +11 -5
- package/dist/job.d.ts +11 -5
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +17 -12
- package/dist/job.js.map +1 -1
- package/dist/llm/llm.cjs +4 -1
- package/dist/llm/llm.cjs.map +1 -1
- package/dist/llm/llm.d.ts.map +1 -1
- package/dist/llm/llm.js +4 -1
- package/dist/llm/llm.js.map +1 -1
- package/dist/vad.cjs +3 -0
- package/dist/vad.cjs.map +1 -1
- package/dist/vad.d.ts.map +1 -1
- package/dist/vad.js +3 -0
- package/dist/vad.js.map +1 -1
- package/dist/voice/agent_session.cjs +9 -2
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +9 -2
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/audio_recognition.cjs +3 -0
- package/dist/voice/audio_recognition.cjs.map +1 -1
- package/dist/voice/audio_recognition.d.ts.map +1 -1
- package/dist/voice/audio_recognition.js +3 -0
- package/dist/voice/audio_recognition.js.map +1 -1
- package/dist/worker.cjs +25 -4
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +25 -4
- package/dist/worker.js.map +1 -1
- package/package.json +3 -1
- package/src/ipc/job_proc_lazy_main.ts +8 -6
- package/src/ipc/proc_pool.ts +14 -2
- package/src/ipc/supervised_proc.ts +23 -10
- package/src/job.ts +27 -12
- package/src/llm/llm.ts +4 -2
- package/src/vad.ts +3 -0
- package/src/voice/agent_session.ts +11 -2
- package/src/voice/audio_recognition.ts +5 -0
- package/src/worker.ts +25 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livekit/agents",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "LiveKit Agents - Node.js",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"require": "dist/index.cjs",
|
|
@@ -37,10 +37,12 @@
|
|
|
37
37
|
"@livekit/mutex": "^1.1.1",
|
|
38
38
|
"@livekit/protocol": "^1.29.1",
|
|
39
39
|
"@livekit/typed-emitter": "^3.0.0",
|
|
40
|
+
"@types/pidusage": "^2.0.5",
|
|
40
41
|
"commander": "^12.0.0",
|
|
41
42
|
"heap-js": "^2.6.0",
|
|
42
43
|
"json-schema": "^0.4.0",
|
|
43
44
|
"livekit-server-sdk": "^2.9.2",
|
|
45
|
+
"pidusage": "^4.0.1",
|
|
44
46
|
"pino": "^8.19.0",
|
|
45
47
|
"pino-pretty": "^11.0.0",
|
|
46
48
|
"sharp": "0.34.3",
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
//
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import { Room, RoomEvent } from '@livekit/rtc-node';
|
|
5
|
-
import { randomUUID } from 'node:crypto';
|
|
6
5
|
import { EventEmitter, once } from 'node:events';
|
|
7
6
|
import { pathToFileURL } from 'node:url';
|
|
8
7
|
import type { Logger } from 'pino';
|
|
9
8
|
import { type Agent, isAgent } from '../generator.js';
|
|
10
|
-
import {
|
|
9
|
+
import { JobContext, JobProcess, type RunningJobInfo, runWithJobContextAsync } from '../job.js';
|
|
11
10
|
import { initializeLogger, log } from '../log.js';
|
|
12
|
-
import { Future } from '../utils.js';
|
|
11
|
+
import { Future, shortuuid } from '../utils.js';
|
|
13
12
|
import { defaultInitializeProcessFunc } from '../worker.js';
|
|
14
13
|
import type { InferenceExecutor } from './inference_executor.js';
|
|
15
14
|
import type { IPCMessage } from './message.js';
|
|
@@ -50,7 +49,7 @@ class InfClient implements InferenceExecutor {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
async doInference(method: string, data: unknown): Promise<unknown> {
|
|
53
|
-
const requestId = 'inference_job_'
|
|
52
|
+
const requestId = shortuuid('inference_job_');
|
|
54
53
|
process.send!({ case: 'inferenceRequest', value: { requestId, method, data } });
|
|
55
54
|
this.#requests[requestId] = new PendingInference();
|
|
56
55
|
const resp = await this.#requests[requestId]!.promise;
|
|
@@ -88,7 +87,6 @@ const startJob = (
|
|
|
88
87
|
};
|
|
89
88
|
|
|
90
89
|
const ctx = new JobContext(proc, info, room, onConnect, onShutdown, new InfClient());
|
|
91
|
-
new CurrentJobContext(ctx);
|
|
92
90
|
|
|
93
91
|
const task = new Promise<void>(async () => {
|
|
94
92
|
const unconnectedTimeout = setTimeout(() => {
|
|
@@ -99,7 +97,11 @@ const startJob = (
|
|
|
99
97
|
);
|
|
100
98
|
}
|
|
101
99
|
}, 10000);
|
|
102
|
-
|
|
100
|
+
|
|
101
|
+
// Run the job function within the AsyncLocalStorage context
|
|
102
|
+
await runWithJobContextAsync(ctx, () => func(ctx)).finally(() => {
|
|
103
|
+
clearTimeout(unconnectedTimeout);
|
|
104
|
+
});
|
|
103
105
|
|
|
104
106
|
await once(closeEvent, 'close').then((close) => {
|
|
105
107
|
logger.debug('shutting down');
|
package/src/ipc/proc_pool.ts
CHANGED
|
@@ -115,7 +115,12 @@ export class ProcPool {
|
|
|
115
115
|
unlock();
|
|
116
116
|
await proc.join();
|
|
117
117
|
} finally {
|
|
118
|
-
this.executors.
|
|
118
|
+
const procIndex = this.executors.indexOf(proc);
|
|
119
|
+
if (procIndex !== -1) {
|
|
120
|
+
this.executors.splice(procIndex, 1);
|
|
121
|
+
} else {
|
|
122
|
+
throw new Error(`proc ${proc} not found in executors`);
|
|
123
|
+
}
|
|
119
124
|
}
|
|
120
125
|
}
|
|
121
126
|
|
|
@@ -134,7 +139,14 @@ export class ProcPool {
|
|
|
134
139
|
this.procUnlock = await this.procMutex.lock();
|
|
135
140
|
const task = this.procWatchTask();
|
|
136
141
|
this.tasks.push(task);
|
|
137
|
-
task.finally(() =>
|
|
142
|
+
task.finally(() => {
|
|
143
|
+
const taskIndex = this.tasks.indexOf(task);
|
|
144
|
+
if (taskIndex !== -1) {
|
|
145
|
+
this.tasks.splice(taskIndex, 1);
|
|
146
|
+
} else {
|
|
147
|
+
throw new Error(`task ${task} not found in tasks`);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
152
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: Apache-2.0
|
|
4
4
|
import type { ChildProcess } from 'node:child_process';
|
|
5
5
|
import { once } from 'node:events';
|
|
6
|
+
import pidusage from 'pidusage';
|
|
6
7
|
import type { RunningJobInfo } from '../job.js';
|
|
7
8
|
import { log, loggerOptions } from '../log.js';
|
|
8
9
|
import { Future } from '../utils.js';
|
|
@@ -25,7 +26,7 @@ export abstract class SupervisedProc {
|
|
|
25
26
|
#runningJob?: RunningJobInfo = undefined;
|
|
26
27
|
proc?: ChildProcess;
|
|
27
28
|
#pingInterval?: ReturnType<typeof setInterval>;
|
|
28
|
-
#
|
|
29
|
+
#memoryMonitorInterval?: ReturnType<typeof setInterval>;
|
|
29
30
|
#pongTimeout?: ReturnType<typeof setTimeout>;
|
|
30
31
|
protected init = new Future();
|
|
31
32
|
#join = new Future();
|
|
@@ -90,8 +91,8 @@ export abstract class SupervisedProc {
|
|
|
90
91
|
this.#join.resolve();
|
|
91
92
|
}, this.#opts.pingTimeout);
|
|
92
93
|
|
|
93
|
-
this.#
|
|
94
|
-
const memoryMB =
|
|
94
|
+
this.#memoryMonitorInterval = setInterval(async () => {
|
|
95
|
+
const memoryMB = await this.getChildMemoryUsageMB();
|
|
95
96
|
if (this.#opts.memoryLimitMB > 0 && memoryMB > this.#opts.memoryLimitMB) {
|
|
96
97
|
this.#logger
|
|
97
98
|
.child({ memoryUsageMB: memoryMB, memoryLimitMB: this.#opts.memoryLimitMB })
|
|
@@ -104,9 +105,9 @@ export abstract class SupervisedProc {
|
|
|
104
105
|
memoryWarnMB: this.#opts.memoryWarnMB,
|
|
105
106
|
memoryLimitMB: this.#opts.memoryLimitMB,
|
|
106
107
|
})
|
|
107
|
-
.
|
|
108
|
+
.warn('process memory usage is high');
|
|
108
109
|
}
|
|
109
|
-
});
|
|
110
|
+
}, 5000);
|
|
110
111
|
|
|
111
112
|
const listener = (msg: IPCMessage) => {
|
|
112
113
|
switch (msg.case) {
|
|
@@ -135,9 +136,7 @@ export abstract class SupervisedProc {
|
|
|
135
136
|
this.#logger
|
|
136
137
|
.child({ err })
|
|
137
138
|
.warn('job process exited unexpectedly; this likely means the error above caused a crash');
|
|
138
|
-
|
|
139
|
-
clearInterval(this.#pingInterval);
|
|
140
|
-
clearInterval(this.#memoryWatch);
|
|
139
|
+
this.clearTimers();
|
|
141
140
|
this.#join.resolve();
|
|
142
141
|
});
|
|
143
142
|
|
|
@@ -196,8 +195,7 @@ export abstract class SupervisedProc {
|
|
|
196
195
|
}, this.#opts.closeTimeout);
|
|
197
196
|
await this.#join.await.then(() => {
|
|
198
197
|
clearTimeout(timer);
|
|
199
|
-
|
|
200
|
-
clearInterval(this.#pingInterval);
|
|
198
|
+
this.clearTimers();
|
|
201
199
|
});
|
|
202
200
|
}
|
|
203
201
|
|
|
@@ -208,4 +206,19 @@ export abstract class SupervisedProc {
|
|
|
208
206
|
this.#runningJob = info;
|
|
209
207
|
this.proc!.send({ case: 'startJobRequest', value: { runningJob: info } });
|
|
210
208
|
}
|
|
209
|
+
|
|
210
|
+
private async getChildMemoryUsageMB(): Promise<number> {
|
|
211
|
+
const pid = this.proc?.pid;
|
|
212
|
+
if (!pid) {
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
const stats = await pidusage(pid);
|
|
216
|
+
return stats.memory / (1024 * 1024); // Convert bytes to MB
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private clearTimers() {
|
|
220
|
+
clearTimeout(this.#pongTimeout);
|
|
221
|
+
clearInterval(this.#pingInterval);
|
|
222
|
+
clearInterval(this.#memoryMonitorInterval);
|
|
223
|
+
}
|
|
211
224
|
}
|
package/src/job.ts
CHANGED
|
@@ -10,21 +10,13 @@ import type {
|
|
|
10
10
|
RtcConfiguration,
|
|
11
11
|
} from '@livekit/rtc-node';
|
|
12
12
|
import { ParticipantKind, RoomEvent, TrackKind } from '@livekit/rtc-node';
|
|
13
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
13
14
|
import type { Logger } from 'pino';
|
|
14
15
|
import type { InferenceExecutor } from './ipc/inference_executor.js';
|
|
15
16
|
import { log } from './log.js';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
constructor(proc: JobContext) {
|
|
21
|
-
CurrentJobContext.#current = proc;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static getCurrent(): JobContext {
|
|
25
|
-
return CurrentJobContext.#current;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
18
|
+
// AsyncLocalStorage for job context, similar to Python's contextvars
|
|
19
|
+
const jobContextStorage = new AsyncLocalStorage<JobContext>();
|
|
28
20
|
|
|
29
21
|
/**
|
|
30
22
|
* Returns the current job context.
|
|
@@ -32,13 +24,29 @@ export class CurrentJobContext {
|
|
|
32
24
|
* @throws {Error} if no job context is found
|
|
33
25
|
*/
|
|
34
26
|
export function getJobContext(): JobContext {
|
|
35
|
-
const ctx =
|
|
27
|
+
const ctx = jobContextStorage.getStore();
|
|
36
28
|
if (!ctx) {
|
|
37
29
|
throw new Error('no job context found, are you running this code inside a job entrypoint?');
|
|
38
30
|
}
|
|
39
31
|
return ctx;
|
|
40
32
|
}
|
|
41
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Runs a function within a job context, similar to Python's contextvars.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function runWithJobContext<T>(context: JobContext, fn: () => T): T {
|
|
39
|
+
return jobContextStorage.run(context, fn);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Runs an async function within a job context, similar to Python's contextvars.
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
export function runWithJobContextAsync<T>(context: JobContext, fn: () => Promise<T>): Promise<T> {
|
|
47
|
+
return jobContextStorage.run(context, fn);
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
/** Which tracks, if any, should the agent automatically subscribe to? */
|
|
43
51
|
export enum AutoSubscribe {
|
|
44
52
|
SUBSCRIBE_ALL,
|
|
@@ -89,6 +97,8 @@ export class JobContext {
|
|
|
89
97
|
#logger: Logger;
|
|
90
98
|
#inferenceExecutor: InferenceExecutor;
|
|
91
99
|
|
|
100
|
+
private connected: boolean = false;
|
|
101
|
+
|
|
92
102
|
constructor(
|
|
93
103
|
proc: JobProcess,
|
|
94
104
|
info: RunningJobInfo,
|
|
@@ -191,6 +201,10 @@ export class JobContext {
|
|
|
191
201
|
autoSubscribe: AutoSubscribe = AutoSubscribe.SUBSCRIBE_ALL,
|
|
192
202
|
rtcConfig?: RtcConfiguration,
|
|
193
203
|
) {
|
|
204
|
+
if (this.connected) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
194
208
|
const opts = {
|
|
195
209
|
e2ee,
|
|
196
210
|
autoSubscribe: autoSubscribe == AutoSubscribe.SUBSCRIBE_ALL,
|
|
@@ -215,6 +229,7 @@ export class JobContext {
|
|
|
215
229
|
});
|
|
216
230
|
});
|
|
217
231
|
}
|
|
232
|
+
this.connected = true;
|
|
218
233
|
}
|
|
219
234
|
|
|
220
235
|
/**
|
package/src/llm/llm.ts
CHANGED
|
@@ -215,8 +215,10 @@ export abstract class LLMStream implements AsyncIterableIterator<ChatChunk> {
|
|
|
215
215
|
promptTokens: usage?.promptTokens || 0,
|
|
216
216
|
promptCachedTokens: usage?.promptCachedTokens || 0,
|
|
217
217
|
totalTokens: usage?.totalTokens || 0,
|
|
218
|
-
tokensPerSecond:
|
|
219
|
-
|
|
218
|
+
tokensPerSecond: (() => {
|
|
219
|
+
const durationSeconds = Math.trunc(Number(duration / BigInt(1000000000)));
|
|
220
|
+
return durationSeconds > 0 ? (usage?.completionTokens || 0) / durationSeconds : 0;
|
|
221
|
+
})(),
|
|
220
222
|
};
|
|
221
223
|
this.#llm.emit('metrics_collected', metrics);
|
|
222
224
|
}
|
package/src/vad.ts
CHANGED
|
@@ -167,6 +167,9 @@ export abstract class VADStream implements AsyncIterableIterator<VADEvent> {
|
|
|
167
167
|
}
|
|
168
168
|
break;
|
|
169
169
|
case VADEventType.INFERENCE_DONE:
|
|
170
|
+
inferenceDurationTotal += value.inferenceDuration;
|
|
171
|
+
this.#lastActivityTime = process.hrtime.bigint();
|
|
172
|
+
break;
|
|
170
173
|
case VADEventType.END_OF_SPEECH:
|
|
171
174
|
this.#lastActivityTime = process.hrtime.bigint();
|
|
172
175
|
break;
|
|
@@ -5,6 +5,7 @@ import type { AudioFrame, Room } from '@livekit/rtc-node';
|
|
|
5
5
|
import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
|
|
6
6
|
import { EventEmitter } from 'node:events';
|
|
7
7
|
import type { ReadableStream } from 'node:stream/web';
|
|
8
|
+
import { getJobContext } from '../job.js';
|
|
8
9
|
import { ChatContext, ChatMessage } from '../llm/chat_context.js';
|
|
9
10
|
import type { LLM, RealtimeModel, RealtimeModelError, ToolChoice } from '../llm/index.js';
|
|
10
11
|
import type { LLMError } from '../llm/llm.js';
|
|
@@ -184,6 +185,7 @@ export class AgentSession<
|
|
|
184
185
|
this.agent = agent;
|
|
185
186
|
this._updateAgentState('initializing');
|
|
186
187
|
|
|
188
|
+
const tasks: Promise<void>[] = [];
|
|
187
189
|
// Check for existing input/output configuration and warn if needed
|
|
188
190
|
if (this.input.audio && inputOptions?.audioEnabled !== false) {
|
|
189
191
|
this.logger.warn('RoomIO audio input is enabled but input.audio is already set, ignoring..');
|
|
@@ -209,7 +211,15 @@ export class AgentSession<
|
|
|
209
211
|
});
|
|
210
212
|
this.roomIO.start();
|
|
211
213
|
|
|
212
|
-
|
|
214
|
+
const ctx = getJobContext();
|
|
215
|
+
if (ctx && ctx.room === room && !room.isConnected) {
|
|
216
|
+
this.logger.debug('Auto-connecting to room via job context');
|
|
217
|
+
tasks.push(ctx.connect());
|
|
218
|
+
}
|
|
219
|
+
// TODO(AJS-265): add shutdown callback to job context
|
|
220
|
+
tasks.push(this.updateActivity(this.agent));
|
|
221
|
+
|
|
222
|
+
await Promise.allSettled(tasks);
|
|
213
223
|
|
|
214
224
|
// Log used IO configuration
|
|
215
225
|
this.logger.debug(
|
|
@@ -220,7 +230,6 @@ export class AgentSession<
|
|
|
220
230
|
`using transcript io: \`AgentSession\` -> ${this.output.transcription ? '`' + this.output.transcription.constructor.name + '`' : '(none)'}`,
|
|
221
231
|
);
|
|
222
232
|
|
|
223
|
-
this.logger.debug('AgentSession started');
|
|
224
233
|
this.started = true;
|
|
225
234
|
this._updateAgentState('listening');
|
|
226
235
|
}
|
|
@@ -367,6 +367,11 @@ export class AudioRecognition {
|
|
|
367
367
|
this.hooks.onStartOfSpeech(ev);
|
|
368
368
|
this.speaking = true;
|
|
369
369
|
|
|
370
|
+
// Capture sample rate from the first VAD event if not already set
|
|
371
|
+
if (ev.frames.length > 0 && ev.frames[0]) {
|
|
372
|
+
this.sampleRate = ev.frames[0].sampleRate;
|
|
373
|
+
}
|
|
374
|
+
|
|
370
375
|
this.bounceEOUTask?.cancel();
|
|
371
376
|
break;
|
|
372
377
|
case VADEventType.INFERENCE_DONE:
|
package/src/worker.ts
CHANGED
|
@@ -190,7 +190,7 @@ export class WorkerOptions {
|
|
|
190
190
|
port = undefined,
|
|
191
191
|
logLevel = 'info',
|
|
192
192
|
production = false,
|
|
193
|
-
jobMemoryWarnMB =
|
|
193
|
+
jobMemoryWarnMB = 500,
|
|
194
194
|
jobMemoryLimitMB = 0,
|
|
195
195
|
}: {
|
|
196
196
|
/**
|
|
@@ -567,7 +567,14 @@ export class Worker {
|
|
|
567
567
|
if (!msg.message.value.job) return;
|
|
568
568
|
const task = this.#availability(msg.message.value);
|
|
569
569
|
this.#tasks.push(task);
|
|
570
|
-
task.finally(() =>
|
|
570
|
+
task.finally(() => {
|
|
571
|
+
const taskIndex = this.#tasks.indexOf(task);
|
|
572
|
+
if (taskIndex !== -1) {
|
|
573
|
+
this.#tasks.splice(taskIndex, 1);
|
|
574
|
+
} else {
|
|
575
|
+
throw new Error(`task ${task} not found in tasks`);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
571
578
|
break;
|
|
572
579
|
}
|
|
573
580
|
case 'assignment': {
|
|
@@ -585,7 +592,14 @@ export class Worker {
|
|
|
585
592
|
case 'termination': {
|
|
586
593
|
const task = this.#termination(msg.message.value);
|
|
587
594
|
this.#tasks.push(task);
|
|
588
|
-
task.finally(() =>
|
|
595
|
+
task.finally(() => {
|
|
596
|
+
const taskIndex = this.#tasks.indexOf(task);
|
|
597
|
+
if (taskIndex !== -1) {
|
|
598
|
+
this.#tasks.splice(taskIndex, 1);
|
|
599
|
+
} else {
|
|
600
|
+
throw new Error(`task ${task} not found in tasks`);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
589
603
|
break;
|
|
590
604
|
}
|
|
591
605
|
}
|
|
@@ -737,7 +751,14 @@ export class Worker {
|
|
|
737
751
|
|
|
738
752
|
const task = jobRequestTask();
|
|
739
753
|
this.#tasks.push(task);
|
|
740
|
-
task.finally(() =>
|
|
754
|
+
task.finally(() => {
|
|
755
|
+
const taskIndex = this.#tasks.indexOf(task);
|
|
756
|
+
if (taskIndex !== -1) {
|
|
757
|
+
this.#tasks.splice(taskIndex, 1);
|
|
758
|
+
} else {
|
|
759
|
+
throw new Error(`task ${task} not found in tasks`);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
741
762
|
}
|
|
742
763
|
|
|
743
764
|
async #termination(msg: JobTermination) {
|