blob: b88556413ba12fa49dd29525fccb0153f60387ec [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ACTOR_ACTOR_TASK_H_
#define CHROME_BROWSER_ACTOR_ACTOR_TASK_H_
#include <iosfwd>
#include <memory>
#include <optional>
#include <vector>
#include "base/callback_list.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/elapsed_timer.h"
#include "base/types/pass_key.h"
#include "build/build_config.h"
#include "chrome/browser/actor/actor_task_delegate.h"
#include "chrome/browser/actor/aggregated_journal.h"
#include "chrome/browser/actor/tools/tool_request.h"
#include "chrome/common/actor.mojom-forward.h"
#include "chrome/common/actor/task_id.h"
#include "chrome/common/actor_webui.mojom.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/visibility.h"
#include "content/public/common/buildflags.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
class Profile;
namespace actor {
class ActionTrackerForMetrics;
class ActorKeyedService;
class ExecutionEngine;
namespace ui {
class UiEventDispatcher;
}
struct ActionResultWithLatencyInfo;
// Represents a task that Chrome is executing on behalf of the user.
//
// ActorTask tracks the state of a single interaction session and takes place
// over multiple "turns" (calls to Act()). Browser tabs that are involved in the
// task are added to the set of "controlled" tabs. ActorTask can be in one of
// three high level states:
//
// * ActorControl: Only the actor is able to interact with controlled tabs
// * UserControl: Only the user is able to interact with controlled tabs
// * Completed: The task is no longer running.
//
// The task is created under actor control. It may be paused or resumed to move
// between actor and user control.
class ActorTask {
public:
using ActCallback =
base::OnceCallback<void(mojom::ActionResultPtr,
std::optional<size_t>,
std::vector<ActionResultWithLatencyInfo>)>;
ActorTask() = delete;
ActorTask(Profile* profile,
std::unique_ptr<ExecutionEngine> execution_engine,
std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher,
webui::mojom::TaskOptionsPtr options = nullptr,
base::WeakPtr<ActorTaskDelegate> delegate = nullptr);
ActorTask(const ActorTask&) = delete;
ActorTask& operator=(const ActorTask&) = delete;
~ActorTask();
// Can only be called by ActorKeyedService
void SetId(base::PassKey<ActorKeyedService>, TaskId id);
TaskId id() const { return id_; }
// Can only be called by unit tests.
void SetIdForTesting(int id);
const std::string& title() const { return title_; }
base::WeakPtr<ActorTaskDelegate> delegate() const { return delegate_; }
// Once `state_` leaves kCreated it should never go back. Once `state_` enters
// kFinished, kCancelled, or kFailed it should never change. These states are
// granular, prefer using the Is[Actor|User]Controlled and IsCompleted methods
// rather than querying `state_` directly.
//
// LINT.IfChange(State)
// These enum values are persisted to logs. Do not renumber or reuse numeric
// values.
enum class State {
kCreated = 0,
kActing = 1,
kReflecting = 2,
kPausedByActor = 3,
kPausedByUser = 4,
kCancelled = 5,
kFinished = 6,
kWaitingOnUser = 7,
kFailed = 8,
kMaxValue = kFailed,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/actor/histograms.xml:ActorTaskState)
// LINT.IfChange(StoppedReason)
// The reason a task was stopped.
enum class StoppedReason {
kStoppedByUser = 0,
kTaskComplete = 1,
kModelError = 2,
kChromeFailure = 3,
kTabDetached = 4,
kShutdown = 5,
kUserStartedNewChat = 6,
kUserLoadedPreviousChat = 7,
kMaxValue = kUserLoadedPreviousChat,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/actor/histograms.xml:StoppedReason,
// //tools/metrics/histograms/metadata/actor/enums.xml:StoppedReasonEnum)
State GetState() const;
// TODO(bokan): This should be private (this class must be in control of its
// state) but is used by tests. Make the tests friends (or update the tests)
// and remove it from the public interface.
void SetState(State new_state);
base::Time GetEndTime() const;
void Act(std::vector<std::unique_ptr<ToolRequest>>&& actions,
ActCallback callback);
// Sets State to `stop_reason` and cancels any pending actions.
// TODO(bokan): It's important that Stop only be called from ActorKeyedService
// since that has to clean up actor tasks. Add a PassKey.
void Stop(StoppedReason stop_reason);
// Pause() is called to indicate that either the actor or user is pausing
// actor actions, determined by the `from_actor` flag. This will cancel any
// in-progress action.
void Pause(bool from_actor);
// Resume() puts the task back into an actor-controlled state. The caller is
// responsible for updating the actor with the latest state of the browser.
void Resume();
// Indicate the task is blocked waiting for user input. The task remains in an
// actor-controlled state and user interaction is still prevented.
void Interrupt();
// Uninterrupt from waiting on user input.
void Uninterrupt(State resumed_state);
// Returns true if the task hasn't completed and is under control of the user.
// That is, the actor cannot send actions and the user is able to interact
// with the task's tabs. i.e. the task is "paused".
bool IsUnderUserControl() const;
// Returns true if the task hasn't completed and is under control of the
// actor. That is, the user is unable to interact with the task's tabs.
bool IsUnderActorControl() const;
// Returns true if the task has completed, either successfully or cancelled.
bool IsCompleted() const;
static bool IsCompletedState(State state);
ExecutionEngine* GetExecutionEngine() const;
// Add/remove the given TabHandle to the set of tabs this task is operating
// over and notify the UI if this is a new tab for the task. Added tabs will
// enter actuation mode and be kept as visible.
using AddTabCallback = base::OnceCallback<void(mojom::ActionResultPtr)>;
void AddTab(tabs::TabHandle tab, AddTabCallback callback);
void RemoveTab(tabs::TabHandle tab);
// Transient version of the above. The tab will enter the same
// simulated-visible state but only until the next call to Act. Until then it
// will always be be included in the LastActedTabs set.
void ObserveTabOnce(tabs::TabHandle tab_handle);
// Returns true if the given tab is part of this task's tab set.
bool HasTab(tabs::TabHandle tab) const;
// Returns true if the given tab is part of this task's controlled tab set and
// the task is under actor control.
bool IsActingOnTab(tabs::TabHandle tab) const;
using TabHandleSet = absl::flat_hash_set<tabs::TabHandle>;
// The set of tabs that have been acted on at any point during this task.
TabHandleSet GetTabs() const;
// The set of tabs that were acted on by the last call to Act.
TabHandleSet GetLastActedTabs() const;
void SetExecutionEngineForTesting(std::unique_ptr<ExecutionEngine> engine);
base::WeakPtr<ActorTask> GetWeakPtr();
private:
class ActorControlledTabState : public content::WebContentsObserver {
public:
explicit ActorControlledTabState(ActorTask* task);
~ActorControlledTabState() override;
void SetContents(content::WebContents* web_contents);
// content::WebContentsObserver overrides
void PrimaryPageChanged(content::Page& page) override;
void OnVisibilityChanged(content::Visibility visibility) override;
// Parent task
raw_ptr<ActorTask> task;
// Keeps the tab in "actuation mode". The runner is present when the tab is
// actively being kept awake and is reset during pause.
base::ScopedClosureRunner actuation_runner;
// When a tab is active, external popup menus are disabled. This runner
// allows external popups to be created again.
#if BUILDFLAG(IS_MAC) && BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
base::ScopedClosureRunner reenable_external_popups;
#endif // BUILDFLAG(IS_MAC) && BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
// Subscription for TabInterface::WillDetach.
base::CallbackListSubscription will_detach_subscription;
// Subscription for TabInterface::WillDiscardContents.
base::CallbackListSubscription content_discarded_subscription;
};
// Transitions a tab/contents into a state where only the actor is responsible
// for interacting with the tab.
void DidTabEnterActorControl(tabs::TabHandle handle);
void DidContentsEnterActorControl(ActorControlledTabState* state,
content::WebContents* contents);
// Transitions the tab from being actor controlled back to user being able to
// interact with in.
void DidTabExitActorControl(tabs::TabHandle handle);
void DidContentsExitActorControl(ActorControlledTabState* state,
content::WebContents* contents);
// Callback from TabInterface for when the WebContents change.
void HandleDiscardContents(tabs::TabInterface* tab,
content::WebContents* old_contents,
content::WebContents* new_contents);
void OnFinishedAct(mojom::ActionResultPtr result,
std::optional<size_t> index_of_failed_action,
std::vector<ActionResultWithLatencyInfo> action_results);
void OnTabWillDetach(tabs::TabInterface* tab,
tabs::TabInterface::DetachReason reason);
void ResetToObserveTabsSet();
// Recomputes the visible tab. This is necessary to capture the previous
// visibility state for UpdateVisibilityTimes() when called after
// ActorControlledTabState::OnVisibilityChanged() is fired.
void RecomputeHasVisibleTab();
void UpdateVisibilityTimes();
State state_ = State::kCreated;
raw_ptr<Profile> profile_;
// The time at which the task was created.
base::TimeTicks create_time_;
// The time at which the task was completed or cancelled.
base::Time end_time_;
std::unique_ptr<ActionTrackerForMetrics> action_tracker_for_metrics_;
// There are multiple possible execution engines. For now we only support
// ExecutionEngine.
std::unique_ptr<ExecutionEngine> execution_engine_;
std::unique_ptr<ui::UiEventDispatcher> ui_event_dispatcher_;
TaskId id_;
base::SafeRef<AggregatedJournal> journal_;
// The title does not change for the duration of a task.
const std::string title_;
// The callback to notify the client of the result of calling Act().
ActCallback callback_for_act_;
// A timer for the current state.
base::ElapsedTimer current_state_timer_;
// An accumulation of elapsed times for previous "active" states. i.e. the
// actor is controlling the task and not waiting on a user action.
base::TimeDelta total_actor_controlled_active_time_;
// A timer for the current actuation period.
base::ElapsedTimer visibility_timer_;
// Whether any of the controlled tabs is visible.
bool has_visible_tab_ = false;
// Total time this task has been actuating while a tab was visible.
base::TimeDelta total_time_visible_;
// Total time this task has been actuating with no tabs visible.
base::TimeDelta total_time_not_visible_;
// A map from a tab's handle to state associated with that tab. The presence
// of a tab in this map signifies that it is part of this task.
absl::flat_hash_map<tabs::TabHandle, std::unique_ptr<ActorControlledTabState>>
controlled_tabs_;
// An additional set of tabs to capture for observations at the end of an Act
// turn. Reset at the beginning of each call to Act.
absl::flat_hash_map<tabs::TabHandle, std::unique_ptr<ActorControlledTabState>>
to_observe_tabs_;
// Running number of actions taken in the current state.
size_t actions_in_current_state_ = 0;
// Running number of actions this task has taken.
size_t total_number_of_actions_ = 0;
// Number of interruptions
size_t total_number_of_interruptions_ = 0;
// Once a task is stopped what the reason was.
std::optional<StoppedReason> stopped_reason_;
// Delegate for task-related events.
base::WeakPtr<ActorTaskDelegate> delegate_;
base::WeakPtrFactory<ui::UiEventDispatcher> ui_weak_ptr_factory_;
base::WeakPtrFactory<ActorTask> weak_ptr_factory_{this};
};
std::ostream& operator<<(std::ostream& os, const ActorTask::State& state);
std::string ToString(const ActorTask::State& state);
} // namespace actor
#endif // CHROME_BROWSER_ACTOR_ACTOR_TASK_H_