From 316671ce1784d9cc64c1466e3cd6b0fa98748381 Mon Sep 17 00:00:00 2001 From: Arthur d'Avray Date: Fri, 5 Jun 2026 10:23:51 +0200 Subject: [PATCH 1/2] refactor: wait for all the tasks at once --- src/commands/tasks.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/commands/tasks.ts b/src/commands/tasks.ts index eb35f18..187947b 100644 --- a/src/commands/tasks.ts +++ b/src/commands/tasks.ts @@ -12,6 +12,7 @@ import type { import { resolveGlobals } from '../client'; import { exitWithError, printLines } from '../output/errors'; import { formatJson } from '../output/json'; +import { startSpinner } from '../output/spinner'; import { formatTaskErrorLine } from '../output/task-errors'; import { formatTask, @@ -24,6 +25,7 @@ import { createPollIntervalOption, createTimeoutOption, type PollTaskResult, + pollTask, printTimeoutHint, waitForTask, } from './async-task'; @@ -183,9 +185,21 @@ async function runTasksCreate(options: TaskCreateCommandOptions, command: Comman const tasks = await client.createTasks(requests); if (options.wait) { - const results = []; - for (const task of tasks) { - results.push(await waitForGenericTask(task.id, options, id => client.getTask(id), json)); + const stopSpinner = json ? () => {} : startSpinner(`Waiting for ${tasks.length} task(s)...`); + let results: PollTaskResult[]; + try { + results = await Promise.all( + tasks.map(task => + pollTask({ + getTask: id => client.getTask(id), + id: task.id, + intervalMs: options.pollInterval * 1000, + timeoutMs: options.timeout * 1000, + }), + ), + ); + } finally { + stopSpinner(); } if (json) { From 2b387c9f4f20312f46889d2ef4485166d86703ff Mon Sep 17 00:00:00 2001 From: Arthur d'Avray Date: Fri, 5 Jun 2026 10:24:31 +0200 Subject: [PATCH 2/2] test: add integration test for the new tasks behavior --- src/__tests__/tasks.integration.test.ts | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/__tests__/tasks.integration.test.ts b/src/__tests__/tasks.integration.test.ts index 6a92079..6765ed3 100644 --- a/src/__tests__/tasks.integration.test.ts +++ b/src/__tests__/tasks.integration.test.ts @@ -48,6 +48,58 @@ describe('tasks command integration', () => { expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('"status": "completed"')); }); + it('creates multiple tasks and waits for all concurrently', async () => { + const dir = mkdtempSync(join(tmpdir(), 'linkup-tasks-integration-')); + const taskFile = join(dir, 'tasks.json'); + const firstInput = { outputType: 'sourcedAnswer', query: 'first' } as const; + const secondInput = { outputType: 'sourcedAnswer', query: 'second' } as const; + writeFileSync( + taskFile, + JSON.stringify([ + { input: firstInput, type: 'search' }, + { input: secondInput, type: 'search' }, + ]), + ); + + const fakeClient = createFakeClient(); + fakeClient.createTasks.mockResolvedValue([ + makeTask({ id: 'task-a', input: firstInput }), + makeTask({ id: 'task-b', input: secondInput }), + ]); + + const calledIds: string[] = []; + let releaseAll!: () => void; + const bothCalled = new Promise(resolve => { + releaseAll = resolve; + }); + fakeClient.getTask.mockImplementation((id: string) => { + calledIds.push(id); + if (calledIds.length === 2) { + releaseAll(); + } + return bothCalled.then(() => + makeTask({ + id, + output: { answer: id, sources: [] }, + status: 'completed', + updatedAt: '2026-01-01T00:00:01.000Z', + }), + ); + }); + + mockGlobals(fakeClient); + const { logSpy } = captureConsole(); + await run(['node', 'linkup', '--json', 'tasks', 'create', '--file', taskFile, '--wait']); + + expect(fakeClient.getTask).toHaveBeenCalledTimes(2); + expect(fakeClient.getTask).toHaveBeenCalledWith('task-a'); + expect(fakeClient.getTask).toHaveBeenCalledWith('task-b'); + + const [output] = logSpy.mock.calls[0]; + const tasks = JSON.parse(output); + expect(tasks.map((task: { id: string }) => task.id)).toEqual(['task-a', 'task-b']); + }); + it('maps list filters and sorting to sdk params', async () => { const fakeClient = createFakeClient(); fakeClient.listTasks.mockResolvedValue({