dify = $dify ?? new FollowupAudioDify(); } public function runOnce(): bool { if (!FollowupAudioStore::enabled() || !FollowupAudioStore::verified() || !($task = FollowupAudioStore::claim())) { return false; } $id = (int) $task['id']; $token = (string) $task['lease_token']; $started = (int) ($task['upstream_started_at'] ?? 0) > 0; try { if (!FollowupAudioStore::verified((string) $task['model_key'])) { throw new FollowupAudioException('AUDIO_NOT_VERIFIED'); } $heartbeat = function (array $fields = []) use ($task, $id, $token, &$started): bool { if (!FollowupAudioStore::enabled() || !FollowupAudioStore::verified((string) $task['model_key'])) { return false; } $actor = PrescriptionAiAccess::actor((int) $task['actor_id']); if (!$actor) { return false; } FollowupAudioAccess::task($id, (int) $task['actor_id'], $actor); if (!FollowupAudioStore::heartbeat($id, $token)) { return false; } if ($fields !== [] && !FollowupAudioStore::checkpoint($id, $token, $fields)) { return false; } if (!empty($fields['upstream_started_at'])) { $started = true; } return true; }; if (!$heartbeat()) { throw new FollowupAudioException('ACCESS_REVOKED', $started); } $extraction = $this->dify->analyze($task, $heartbeat); if (!$heartbeat()) { throw new FollowupAudioException('LEASE_LOST', true); } if (!FollowupAudioStore::complete($id, $token, $extraction)) { throw new FollowupAudioException('LEASE_LOST', true); } } catch (FollowupAudioException $e) { FollowupAudioStore::fail($id, $token, $e->errorCode, $e->getMessage(), $e->uncertain); } catch (\Throwable $e) { // The exception may contain SQL, names, transcript, credentials or a signed URL. FollowupAudioStore::fail($id, $token, 'INTERNAL_ERROR', '音频任务执行异常,请核对任务状态', $started); } return true; } }