diff --git a/src/mgr/queue-dispatch.ts b/src/mgr/queue-dispatch.ts index b011ce4..9a2b00d 100644 --- a/src/mgr/queue-dispatch.ts +++ b/src/mgr/queue-dispatch.ts @@ -82,14 +82,20 @@ function assertDispatchable(task: QueueTaskRecord): void { } function queueStateFromCore(run: RunRecord, command: CommandRecord): QueueTaskState { + const runState = queueTerminalStateFromRun(run); + if (runState) return runState; if (command.state === "completed") return "completed"; if (command.state === "failed") return "failed"; if (command.state === "cancelled") return "cancelled"; + return "running"; +} + +function queueTerminalStateFromRun(run: RunRecord): QueueTaskState | null { if (run.status === "completed") return "completed"; if (run.status === "failed") return "failed"; if (run.status === "blocked") return "blocked"; if (run.status === "cancelled") return "cancelled"; - return "running"; + return null; } function buildRunInput(task: QueueTaskRecord, input: JsonRecord): CreateRunInput { diff --git a/src/selftest/cases/75-queue-q2-dispatch.ts b/src/selftest/cases/75-queue-q2-dispatch.ts index 6639fd1..3ce89ea 100644 --- a/src/selftest/cases/75-queue-q2-dispatch.ts +++ b/src/selftest/cases/75-queue-q2-dispatch.ts @@ -165,6 +165,37 @@ process.exit(1); assert.equal(runnerEnvValue(unideskManifest, "UNIDESK_SSH_CLIENT_TOKEN"), "secretRef"); assertNoSecretLeak(unideskDispatched); + const blockedCreated = await client.post("/api/v1/queue/tasks", { + tenantId: "hwlab", + projectId: "pikasTech/HWLAB", + queue: "dev", + lane: "q2", + title: "Q2 queue blocked task", + priority: 23, + backendProfile: "codex", + providerId: "G14", + workspaceRef: { kind: "host-path", path: context.workspace }, + sessionRef: { sessionId: "sess_queue_q2_blocked_selftest", metadata: { source: "queue-q2-blocked-self-test" } }, + executionPolicy: { + sandbox: "workspace-write", + approval: "never", + timeoutMs: 15_000, + network: "default", + secretScope: { allowCredentialEcho: false, providerCredentials: [{ profile: "codex", secretRef: { name: "agentrun-v01-provider-codex", keys: ["auth.json", "config.toml"], mountPath: context.codexHome } }] }, + }, + resourceBundleRef: null, + payload: { prompt: "queue blocked hello" }, + references: [{ kind: "issue", url: "https://github.com/pikasTech/agentrun/issues/130" }], + metadata: { source: "queue-q2-blocked-self-test" }, + idempotencyKey: "queue-q2-blocked-self-test", + }) as QueueTaskRecord; + const blockedDispatched = await client.post(`/api/v1/queue/tasks/${blockedCreated.id}/dispatch`, { attemptId: "attempt_queue_q2_blocked_selftest" }) as QueueDispatchResult; + await client.patch(`/api/v1/commands/${blockedDispatched.command.id}/status`, { terminalStatus: "blocked", failureKind: "required-skill-unavailable", failureMessage: "missing required skill" }); + await client.patch(`/api/v1/runs/${blockedDispatched.run.id}/status`, { terminalStatus: "blocked", failureKind: "required-skill-unavailable", failureMessage: "missing required skill" }); + const blockedShown = await client.get(`/api/v1/queue/tasks/${blockedCreated.id}`) as QueueTaskRecord; + assert.equal(blockedShown.state, "blocked"); + assert.equal(blockedShown.latestAttempt?.state, "blocked"); + const cancelCreated = await client.post("/api/v1/queue/tasks", { tenantId: "unidesk", projectId: "pikasTech/unidesk", @@ -211,7 +242,7 @@ process.exit(1); assert.ok(JSON.stringify(cancelManifest).includes(cancelDispatched.run.id)); assertNoSecretLeak(dispatched); assertNoSecretLeak(cancelled); - return { name: "queue-q2-dispatch", tests: ["queue-dispatch-run-command-runner-job", "queue-read-views-refresh-terminal-state", "queue-refresh-from-core-status", "queue-dispatch-no-repeat", "queue-unidesk-ssh-endpoint-auto-env", "queue-cancel-propagates-to-run-command-session"] }; + return { name: "queue-q2-dispatch", tests: ["queue-dispatch-run-command-runner-job", "queue-read-views-refresh-terminal-state", "queue-refresh-from-core-status", "queue-dispatch-no-repeat", "queue-unidesk-ssh-endpoint-auto-env", "queue-blocked-run-state-wins-over-command-failed", "queue-cancel-propagates-to-run-command-session"] }; } finally { await new Promise((resolve) => server.server.close(() => resolve())); }