usecase

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 67 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SlackActionIDStatusSelect   = "hc_action_status"
	SlackActionIDAssigneeSelect = "hc_action_assignee"
)

Slack interaction action / block IDs for Action Block Kit messages.

View Source
const (
	// SlackAgentSessionActionsID is the actionID for the agent session overflow menu.
	// Slack sends this in block_actions callbacks when any menu option is selected.
	SlackAgentSessionActionsID = "hc_agent_session_actions"

	// SlackAgentActionShowSessionInfo is the option value prefix for showing session info modal.
	// Full value format: "show_session_info:{sessionID}"
	SlackAgentActionShowSessionInfo = "show_session_info"
)

Slack interaction constants for agent session actions

View Source
const (
	CaseIDKey       = "case_id"
	ActionIDKey     = "action_id"
	ActionStepIDKey = "action_step_id"
	// MissingFieldIDsKey / MissingFieldNamesKey are populated on
	// ErrMissingRequiredOnSubmit so the GraphQL error mapper can expose the
	// list to the frontend (which renders them as the offending inputs).
	MissingFieldIDsKey   = "missing_field_ids"
	MissingFieldNamesKey = "missing_field_names"
	// CurrentStatusKey is populated on status-transition errors so the
	// frontend can tell the user the current state of the case (e.g. "this
	// draft has already been opened — refresh to see the change").
	CurrentStatusKey = "current_status"
)

Context keys for error values

View Source
const (
	SlackCallbackIDSelectWorkspace = "hc_select_workspace"
	SlackCallbackIDCreateCase      = "hc_create_case"
	SlackCallbackIDEditCase        = "hc_edit_case"
	// SlackCallbackIDCommandChoice is the modal that lets the user pick
	// "edit case" vs "create action" when /cmd is invoked in a case channel
	// without an explicit subcommand. The "command" wording (rather than
	// "action") avoids collision with Slack interactivity's own "action"
	// concept (block_actions etc.).
	SlackCallbackIDCommandChoice = "hc_command_choice"
	// SlackCallbackIDCreateAction is the modal that creates a new Action
	// (i.e. a task) under the case bound to the current channel.
	SlackCallbackIDCreateAction = "hc_create_action"

	SlackBlockIDWorkspaceSelect  = "hc_ws_select_block"
	SlackActionIDWorkspaceRadio  = "hc_ws_radio"
	SlackBlockIDCaseTitle        = "hc_case_title_block"
	SlackActionIDCaseTitle       = "hc_case_title"
	SlackBlockIDCaseDescription  = "hc_case_desc_block"
	SlackActionIDCaseDescription = "hc_case_desc"
	SlackBlockIDCasePrivate      = "hc_case_private_block"
	SlackActionIDCasePrivate     = "hc_case_private"
	SlackBlockIDCaseAssignees    = "hc_case_assignees_block"
	SlackActionIDCaseAssignees   = "hc_case_assignees"
	// Edit modal's standalone "Options" checkbox group, currently carrying
	// only the Test-case flag (private/draft are creation-only concerns).
	SlackBlockIDCaseTest  = "hc_case_test_block"
	SlackActionIDCaseTest = "hc_case_test"

	// Command choice modal block / action IDs
	SlackBlockIDCommandChoice  = "hc_command_choice_block"
	SlackActionIDCommandChoice = "hc_command_choice_radio"

	// Action creation modal block / action IDs
	SlackBlockIDActionTitle       = "hc_action_title_block"
	SlackActionIDActionTitle      = "hc_action_title"
	SlackBlockIDActionDesc        = "hc_action_desc_block"
	SlackActionIDActionDesc       = "hc_action_desc"
	SlackBlockIDActionAssignee    = "hc_action_assignee_input_block"
	SlackActionIDActionAssignee   = "hc_action_assignee_input"
	SlackBlockIDActionStatusInput = "hc_action_status_input_block"
	SlackActionIDActionStatusIn   = "hc_action_status_input"
	SlackBlockIDActionDueDate     = "hc_action_due_block"
	SlackActionIDActionDueDate    = "hc_action_due"

	// Save-as-Draft is now offered as a "Draft mode" checkbox inside
	// the Case creation modal's Options group rather than as a separate
	// body-level button. These legacy block/action IDs are kept so the
	// dispatcher can still route any in-flight callbacks emitted before
	// the layout change to HandleSaveAsDraftClick.
	SlackBlockIDSaveAsDraftActions = "hc_save_draft_block"
	SlackActionIDSaveAsDraft       = "hc_save_draft_btn"
)

Callback IDs for slash command modals

View Source
const (
	ActionIDDraftSelectWS = "mention_draft_select_ws"
	ActionIDDraftSubmit   = "mention_draft_submit"
	ActionIDDraftEdit     = "mention_draft_edit"
	ActionIDDraftCancel   = "mention_draft_cancel"

	BlockIDDraftWSSelect = "mention_draft_ws_block"
	BlockIDDraftActions  = "mention_draft_actions"
)

PreviewActionIDs identify the buttons inside the preview ephemeral.

View Source
const (
	// ActionIDDraftQuestionChoice is the action_id of the radio_buttons /
	// checkboxes element rendering the choices for a single question item.
	// Same id across all items; per-item disambiguation is done via block_id.
	ActionIDDraftQuestionChoice = "mention_draft_question_choice"
	// ActionIDDraftQuestionSubmit is the action_id of the Submit button.
	// The button's value carries the draft id so the submit handler can
	// re-enter the draft flow without parsing block_ids.
	ActionIDDraftQuestionSubmit = "mention_draft_question_submit"
	// ActionIDDraftQuestionOther is the action_id of the plain_text_input
	// element rendering the per-item free-form fallback. Same id across all
	// items; per-item disambiguation is via block_id (item suffix `:other`).
	ActionIDDraftQuestionOther = "mention_draft_question_other"
	// ActionIDDraftQuestionFreeText is the action_id of the plain_text_input
	// element rendering a `free_text` question item's primary control. The
	// item has no select / checkbox companion; its single multiline input
	// IS the answer surface, so a distinct action_id keeps the parser path
	// clean from the per-item Other fallback used by the closed-list
	// types.
	ActionIDDraftQuestionFreeText = "mention_draft_question_free_text"
	// BlockIDDraftQuestionActions is the block_id of the actions block
	// hosting the Submit button. Distinct from the per-question input
	// block_ids so dispatch can ignore it when matching answers.
	BlockIDDraftQuestionActions = "mention_draft_question_actions"
	// BlockIDDraftQuestionItemPrefix is prepended to each question item's
	// block_id so the submit handler can recognise (and skip) the actions
	// block while iterating state values. Item ID follows the prefix.
	BlockIDDraftQuestionItemPrefix = "mention_draft_question_item:"
	// BlockIDDraftQuestionOtherSuffix is appended to a per-item block_id to
	// form the block_id of its free-text fallback InputBlock. Pairing the
	// fallback against the item is done by stripping this suffix.
	BlockIDDraftQuestionOtherSuffix = ":other"
)

Slack identifiers for the open-mode question form. The action_ids and block_ids are the contract between the rendered Block Kit message and the HTTP interactions controller.

View Source
const ActionIDThreadCreateQuestionSubmit = "thread_create_question_submit"

ActionIDThreadCreateQuestionSubmit is the action_id of the Submit button on the thread-mode case-initialization question form. It is distinct from the open-mode (proposal) submit action so the interactions controller routes it to HandleThreadCaseQuestionSubmit. The button value carries the thread TS so the handler can re-resolve the thread without parsing block_ids. The form's per-item input elements reuse the shared open-mode action_ids (ActionIDDraftQuestion*) so the answer parser (parseDraftQuestionAnswers) is shared.

View Source
const JobRunLogPageDefaultSize = 20

JobRunLogPageDefaultSize is the page size used when the caller does not specify one (or specifies <= 0). Aligned with the design target for the CaseAgent dashboard run-log table.

View Source
const JobRunLogPageMaxSize = 100

JobRunLogPageMaxSize caps the upper bound a caller can request, to keep a single page round-trip small enough to stay snappy.

View Source
const SlackCallbackIDDraftEdit = "mention_draft_edit_modal"

SlackCallbackIDDraftEdit is the modal callback_id for the Case-draft Edit modal opened from the preview ephemeral.

Variables

View Source
var (
	// Not found errors
	ErrCaseNotFound       = errors.New("case not found")
	ErrActionNotFound     = errors.New("action not found")
	ErrActionStepNotFound = errors.New("action step not found")

	// Status errors
	ErrCaseAlreadyClosed = errors.New("case is already closed")
	ErrCaseAlreadyOpen   = errors.New("case is already open")
	// ErrCaseIsDraft is returned when a status-transition operation (close /
	// reopen) is invoked on a case that is still in DRAFT. Drafts only leave
	// DRAFT via SubmitDraft (→ OPEN) or DiscardDraft (delete).
	ErrCaseIsDraft = errors.New("case is in draft state")
	// ErrCaseNotDraft is returned by draft-specific operations (Submit /
	// Discard) when the targeted case is not in DRAFT.
	ErrCaseNotDraft = errors.New("case is not a draft")
	// ErrCaseThreadModeUseStatus is returned by CloseCase / ReopenCase when the
	// targeted case is thread-mode (bound to a Slack thread). Thread-mode cases
	// change lifecycle by moving the configurable board status via
	// UpdateCaseStatus, which keeps BoardStatus and the lifecycle Status in sync;
	// closing / reopening one directly would set Status while leaving BoardStatus
	// on a mismatched column, desyncing the two. The boundary rejects it so a
	// mis-wired caller fails loudly instead of producing the inconsistent state.
	ErrCaseThreadModeUseStatus = errors.New("thread-mode case lifecycle must change via board status")
	// ErrMissingRequiredOnSubmit is returned by SubmitDraft when the draft
	// is missing one or more required custom fields. The wrapping goerr
	// carries the field IDs and human-friendly names (see MissingFieldIDsKey /
	// MissingFieldNamesKey) so the frontend can point the user at exactly
	// which inputs to fill.
	ErrMissingRequiredOnSubmit = errors.New("draft is missing required fields")
	// ErrDraftTitleRequired is returned by SubmitDraft when the draft was
	// saved with an empty title. Slack channel naming requires a non-empty
	// title, so this is enforced at promote time even though Save-as-Draft
	// accepted the empty value.
	ErrDraftTitleRequired = errors.New("draft title is required before submit")
	// ErrFieldValidationFailed wraps a field-level validation failure from
	// the workspace's field schema (option lookup, type coercion, etc.).
	// Used so the GraphQL layer can surface "fix the field" as a specific
	// code without grepping wrapped error messages.
	ErrFieldValidationFailed = errors.New("field validation failed")
	// ErrActivationFailed wraps a post-promotion activation failure (Slack
	// channel creation, channel invites, welcome message). The draft is
	// rolled back to DRAFT before this error returns, so callers / frontends
	// should advise "retry submit".
	ErrActivationFailed = errors.New("case activation failed")

	// ErrCasePrivateThreadModeUnsupported is returned by the case create flows
	// when isPrivate=true is requested for a thread-mode workspace. A private
	// case's only effect is a dedicated private Slack channel, which channel
	// mode creates and thread mode has no equivalent for (thread-mode cases
	// reuse the monitored channel and never carry IsPrivate). The frontend
	// already hides the private toggle in thread mode; the invariant is enforced
	// here at the usecase boundary so every entry point (GraphQL, Slack modal,
	// agent tool, import) is covered, not just the web form.
	ErrCasePrivateThreadModeUnsupported = errors.New("private case is not supported in thread mode")

	// ErrThreadModeSlackUnconfigured is returned when a thread-mode workspace
	// must bring a case to OPEN but the Slack service or the monitored channel
	// is not configured. Thread-mode cases live in the monitored channel's
	// thread and never provision a dedicated channel, so falling back to the
	// channel-mode activation path (which would call CreateChannel) is exactly
	// the invariant violation to avoid. Startup config validation normally
	// rejects a thread-mode workspace without a monitor channel, so this guards
	// direct WorkspaceEntry construction (tests / eval) and fails closed.
	ErrThreadModeSlackUnconfigured = errors.New("thread-mode workspace requires slack service and monitor channel")

	// ErrCaseThreadModeNoActions is returned by ActionUseCase write paths when
	// the parent (or reparent target) Case is thread-mode. Thread-mode cases
	// track progress through the configurable board status (Kanban) and have no
	// Actions — the configurable status attaches to the Case itself there, while
	// in channel mode it attaches to Actions (see model.Case.BoardStatus). The
	// invariant is enforced at the usecase boundary so every entry point
	// (GraphQL, Slack, agent tools, eval) is covered, not just the agent tool
	// wiring that withholds the action tools for thread-mode workspaces.
	ErrCaseThreadModeNoActions = errors.New("thread-mode case cannot have actions")

	// Action Slack-post state errors
	ErrSlackMessageAlreadyPosted = errors.New("action already has a Slack message")
	ErrCaseHasNoSlackChannel     = errors.New("parent case has no Slack channel")

	// Action archive state errors
	ErrActionAlreadyArchived = errors.New("action is already archived")
	ErrActionNotArchived     = errors.New("action is not archived")

	// Access control errors
	ErrAccessDenied = errors.New("access denied to private case")

	// Other errors
	ErrDuplicateField = errors.New("duplicate field")

	// ErrUnknownUser is returned by a case write when an assignee id or a
	// user / multi-user field value references a user that does not exist in
	// the SlackUser store. It guards against an agent (or API client)
	// persisting a hallucinated / mistyped user id. Slack sync delay is
	// treated as non-existence per project policy.
	ErrUnknownUser = errors.New("unknown user")

	// ErrInvalidArgument is returned by usecase methods when the caller
	// provides input that violates a domain invariant (unknown ID, list
	// element that does not belong to the workspace, etc.). Distinct from
	// ErrAccessDenied / ErrNotFound so the GraphQL layer can map it to
	// BAD_USER_INPUT rather than INTERNAL.
	ErrInvalidArgument = errors.New("invalid argument")
)

Sentinel errors for use case layer

View Source
var DefaultThreadcaseBudget = planexec.BudgetConfig{
	PlannerLoopMax:  8,
	SubAgentLoopMax: 20,
}

DefaultThreadcaseBudget is the planexec budget used for thread-mode agent turns when AgentDeps.ThreadcaseBudget is unset. Conservative bounds keep a mention turn responsive while allowing a couple of investigation rounds.

View Source
var ErrGitHubNotConfigured = goerr.New("GitHub App is not configured")

ErrGitHubNotConfigured is returned when GitHub App is not configured

View Source
var ErrImportFieldSchemaStale = goerr.New("workspace field schema has changed since import was created")

ErrImportFieldSchemaStale is returned by ImportUseCase.Execute when the workspace field schema has changed since createCaseImport. The session.Issues will be appended with per-field details.

View Source
var ErrImportSessionInvalidState = goerr.New("import session is not in pending state")

ErrImportSessionInvalidState is returned by ImportUseCase.Execute when the target session is not in pending state.

View Source
var ErrImportSessionNotFound = goerr.New("import session not found")

ErrImportSessionNotFound is returned by ImportUseCase.Get when no session matches the given ID or the caller is not the creator.

View Source
var ErrImportValidation = goerr.New("import session has validation errors")

ErrImportValidation is returned by ImportUseCase.Execute when the session has unresolved error-severity issues from preview.

View Source
var ErrInferenceInProgress = errors.New("draft inference in progress")

ErrInferenceInProgress is returned when an interaction tries to operate on a draft whose Materializer call is still in flight.

View Source
var ErrKnowledgeInput = goerr.New("invalid knowledge input")

ErrKnowledgeInput is returned when create/update input fails validation at the usecase entry point (before any persistence).

View Source
var ErrMemoNotEnabled = goerr.New("memo feature is not enabled for this workspace")

ErrMemoNotEnabled is returned when a memo write targets a workspace that has not configured the memo feature (no [memo] field schema).

View Source
var ErrMemoNotFound = goerr.New("memo not found")

ErrMemoNotFound is returned when a memo cannot be located within its Case.

View Source
var ErrNoAccessibleWorkspace = errors.New("no accessible workspace for user")

ErrNoAccessibleWorkspace is returned when the mentioning user has no workspace they can create a Case in.

View Source
var ErrTagInUse = goerr.New("tag is in use")

ErrTagInUse is returned when deleting a tag that is still referenced by at least one knowledge entry. The delete is refused so no dangling reference can be created.

View Source
var ErrUnauthenticated = goerr.New("unauthenticated")

ErrUnauthenticated is returned by the dashboard "my *" queries when no auth token is present. Unlike ListCases (which treats a missing token as a system/bot context that bypasses per-user filtering), the home aggregation is meaningless without an identity, so it fails loudly instead.

View Source
var ErrUnknownTag = goerr.New("unknown tag id")

ErrUnknownTag is returned when a knowledge create/update references a tag id that does not exist in the workspace. The operation is rejected wholesale — no partial write occurs.

Functions

func NewActionStepToolAdapter

func NewActionStepToolAdapter(uc *ActionStepUseCase) core.ActionStepMutator

NewActionStepToolAdapter returns a core.ActionStepMutator backed by the supplied ActionStepUseCase. Returns nil when uc is nil, matching the contract that core.Deps.ActionStepUC == nil → tool fails loudly.

func NewActionToolAdapter

func NewActionToolAdapter(uc *ActionUseCase) core.ActionMutator

NewActionToolAdapter returns a core.ActionMutator backed by the supplied ActionUseCase. Returns nil when uc is nil, matching the contract that core.Deps.ActionUC == nil → tool fails loudly.

func NewCaseMultiActionAdapter

func NewCaseMultiActionAdapter(action *ActionUseCase, step *ActionStepUseCase) casemulti.ActionUsecase

NewCaseMultiActionAdapter returns a casemulti.ActionUsecase backed by the action + action-step usecases, or nil when either is nil (which disables the casemulti action tools while leaving the case-level tools working).

func NewCaseMultiCaseAdapter

func NewCaseMultiCaseAdapter(uc *CaseUseCase) casemulti.CaseUsecase

NewCaseMultiCaseAdapter returns a casemulti.CaseUsecase backed by uc, or nil when uc is nil so hosts can wire casemulti unconditionally (a nil CaseUC makes casemulti.New return no tools).

func NewCaseToolAdapter

func NewCaseToolAdapter(uc *CaseUseCase) casewriter.CaseMutator

NewCaseToolAdapter wraps a CaseUseCase as a casewriter.CaseMutator. Returns nil when uc is nil so callers can pass the result straight through to casewriter.Deps (a nil mutator disables the tool, which fails loudly at runtime rather than silently degrading).

func NewKnowledgeToolAccessor

func NewKnowledgeToolAccessor(uc *KnowledgeUseCase, tagUC *TagUseCase) knowledgetool.KnowledgeAccessor

NewKnowledgeToolAccessor returns the read surface backed by the use cases. Returns nil when either use case is nil so the tool wiring can detect an unconfigured knowledge feature.

func NewKnowledgeToolMutator

func NewKnowledgeToolMutator(uc *KnowledgeUseCase, tagUC *TagUseCase) knowledgetool.KnowledgeMutator

NewKnowledgeToolMutator returns the write surface backed by the use cases. Returns nil when either use case is nil.

func NewMemoToolAdapter

func NewMemoToolAdapter(uc *MemoUseCase) memotool.MemoMutator

NewMemoToolAdapter returns a memotool.MemoMutator backed by the supplied MemoUseCase. Returns nil when uc is nil so the tool wiring can detect an unconfigured memo feature.

func ParseAgentActionValue

func ParseAgentActionValue(value string) (action string, data string, err error)

ParseAgentActionValue parses an agent action option value into action type and data. Format: "{action}:{data}" (e.g., "show_session_info:uuid-value")

func ParseSlackAssigneeBlockID

func ParseSlackAssigneeBlockID(blockID string) (workspaceID string, actionID int64, err error)

ParseSlackAssigneeBlockID parses a block_id of the form "{prefix}:{workspaceID}:{actionID}" into its components.

func ParseSlackStatusSelectValue

func ParseSlackStatusSelectValue(value string) (workspaceID string, actionID int64, status types.ActionStatus, err error)

ParseSlackStatusSelectValue parses a status_select option value of the form "{workspaceID}:{actionID}:{status}" and returns its components.

func SlackActionAssigneeBlockID

func SlackActionAssigneeBlockID(workspaceID string, actionID int64) string

SlackActionAssigneeBlockID returns the block_id that wraps the users_select element. We encode (workspaceID, actionID) into the block_id because users_select callbacks carry no `value` field.

Types

type ActionStepUseCase

type ActionStepUseCase struct {
	// contains filtered or unexported fields
}

ActionStepUseCase orchestrates ActionStep CRUD: load parent Action and Case, enforce private-case access control, persist the step, record an ActionEvent, and post a thread context-block notification to Slack.

func NewActionStepUseCase

func NewActionStepUseCase(repo interfaces.Repository, slackService slack.Service, slotCoord *notificationSlotCoordinator) *ActionStepUseCase

NewActionStepUseCase constructs the ActionStepUseCase. slotCoord may be nil; when nil (or its slotDuration is non-positive), step change notifications fall back to the legacy reply_broadcast path.

func (*ActionStepUseCase) Add

Add creates a new ActionStep under the given Action.

func (*ActionStepUseCase) Delete

Delete removes a step. Returns nil even if the step did not exist (idempotent) but only after access control is satisfied.

func (*ActionStepUseCase) List

func (uc *ActionStepUseCase) List(ctx context.Context, workspaceID string, actionID int64) ([]*model.ActionStep, error)

List returns all steps for the given Action, ordered oldest first. Returns an empty list when the caller cannot access the parent Case.

func (*ActionStepUseCase) Progress

func (uc *ActionStepUseCase) Progress(ctx context.Context, workspaceID string, actionID int64) (int, int, error)

Progress returns (done, total) for the given Action's steps. Both are 0 when the parent Case is inaccessible to the caller.

func (*ActionStepUseCase) Rename

Rename updates the step's title. No-op (returns the existing step untouched) when the trimmed title equals the current one — no ActionEvent is recorded and no Slack notification is sent in that case.

func (*ActionStepUseCase) SetDone

SetDone toggles the step's done state. No-op (returns the existing step untouched) if the requested state is already in effect.

type ActionUseCase

type ActionUseCase struct {
	// contains filtered or unexported fields
}

func NewActionUseCase

func NewActionUseCase(repo interfaces.Repository, registry *model.WorkspaceRegistry, slackService slack.Service, baseURL string, slotCoord *notificationSlotCoordinator) *ActionUseCase

NewActionUseCase constructs the ActionUseCase. slotCoord may be nil; when nil (or when its slotDuration is non-positive), channel-side change notifications fall back to the legacy reply_broadcast path on each post.

func (*ActionUseCase) ArchiveAction

func (uc *ActionUseCase) ArchiveAction(ctx context.Context, workspaceID string, id int64, actor ActorRef) (*model.Action, error)

ArchiveAction marks an action as archived so it disappears from default listings (Kanban, Case detail). Archived actions can be unarchived later via UnarchiveAction. The action document itself is preserved; only the public lifecycle hides it. Idempotency: archiving an already-archived action returns ErrActionAlreadyArchived rather than silently succeeding, because callers should distinguish "already done" from "now done" for UI feedback.

func (*ActionUseCase) BulkArchiveActions

func (uc *ActionUseCase) BulkArchiveActions(ctx context.Context, workspaceID string, ids []int64, actor ActorRef) ([]*model.Action, error)

BulkArchiveActions archives the given actions by delegating each id to ArchiveAction, so bulk and single archive share the exact same path (access control, idempotency, event recording, Slack notification). The archive body lives only in ArchiveAction and is never duplicated here, so any future change to single-archive behaviour automatically applies to bulk.

Idempotency: ids that are already archived are skipped rather than failing the whole batch, since "archive everything in this column" should tolerate rows that someone else already archived. Any other error (access denied, not found, persistence failure) is propagated — bulk archive must not silently swallow a real failure. The returned slice contains only the actions that were newly archived.

func (*ActionUseCase) BulkArchiveActionsAsync

func (uc *ActionUseCase) BulkArchiveActionsAsync(ctx context.Context, workspaceID string, ids []int64, actor ActorRef)

BulkArchiveActionsAsync runs BulkArchiveActions in the background via async.Dispatch so the operation outlives the HTTP request that triggered it. Clearing a completed column issues one ArchiveAction (DB write + event + Slack post) per action; on a large column that can take longer than the client is willing to wait, and a mid-flight disconnect would otherwise cancel the request context and leave the column half-archived. async.Dispatch hands the work a context with the auth token / logger intact but the cancellation severed, so the whole column is archived even after the GraphQL response (the accepted ids) has been returned. Per-action failures surface through errutil.Handle inside the synchronous core rather than to the caller, which has already returned.

func (*ActionUseCase) CreateAction

func (uc *ActionUseCase) CreateAction(ctx context.Context, workspaceID string, caseID int64, title, description string, assigneeID string, slackMessageTS string, status types.ActionStatus, dueDate *time.Time) (*model.Action, error)

func (*ActionUseCase) GetAction

func (uc *ActionUseCase) GetAction(ctx context.Context, workspaceID string, id int64, opts ...interfaces.ActionListOptions) (*model.Action, error)

GetAction returns the action by ID. opts is variadic and optional: existing callers pass none and get the action regardless of its parent Case's privacy (archive scope is irrelevant to a single Get). When the first opts element has ExcludePrivateCaseActions set, the parent Case is loaded and an action belonging to a private Case is reported as ErrAccessDenied — the MCP endpoint relies on this so private-case actions are never returned.

func (*ActionUseCase) GetActions

func (uc *ActionUseCase) GetActions(ctx context.Context, workspaceID string, ids []int64, opts interfaces.ActionListOptions) ([]*model.Action, error)

GetActions retrieves multiple actions by ID in a single repository batch (GetByIDs) and applies the same parent-Case access control as the listing methods, avoiding the N+1 round-trips a per-ID loop would incur. Results preserve the order of ids; ids that do not resolve — and actions whose parent Case is excluded (private under opts.ExcludePrivateCaseActions, or inaccessible to the caller) — are omitted.

func (*ActionUseCase) GetActionsByCase

func (uc *ActionUseCase) GetActionsByCase(ctx context.Context, workspaceID string, caseID int64, opts interfaces.ActionListOptions) ([]*model.Action, error)

func (*ActionUseCase) ListActions

func (uc *ActionUseCase) ListActions(ctx context.Context, workspaceID string, opts interfaces.ActionListOptions) ([]*model.Action, error)

func (*ActionUseCase) ListOpenCaseActions

func (uc *ActionUseCase) ListOpenCaseActions(ctx context.Context, workspaceID string) ([]*model.Action, error)

ListOpenCaseActions returns all actions belonging to cases with OPEN status.

func (*ActionUseCase) PostSlackMessageToAction

func (uc *ActionUseCase) PostSlackMessageToAction(ctx context.Context, workspaceID string, actionID int64) (*model.Action, error)

PostSlackMessageToAction posts the Action's primary Slack message and persists the resulting timestamp on the Action. It exists for two callers that operate on actions whose initial CreateAction post never reached Slack:

  • The WebUI "Post to Slack" button on actions whose SlackMessageTS is empty (e.g. the action was created before the tool path was unified with the usecase).
  • The diagnosis fix-unsent-action job, which sweeps such actions in bulk.

Unlike CreateAction's best-effort tail, this method is strict: a missing Slack channel, an already-posted action, or a Slack/PostMessage error surface as typed errors so the caller can react. Callers that want CreateAction-style "log and move on" behaviour should call CreateAction instead.

func (*ActionUseCase) UnarchiveAction

func (uc *ActionUseCase) UnarchiveAction(ctx context.Context, workspaceID string, id int64, actor ActorRef) (*model.Action, error)

UnarchiveAction restores a previously archived action back to active state.

func (*ActionUseCase) UpdateAction

func (uc *ActionUseCase) UpdateAction(ctx context.Context, workspaceID string, in UpdateActionInput) (*model.Action, error)

UpdateAction is the single entry point for mutating an Action. All transports (GraphQL/WebUI, Slack interactivity, internal callers) funnel through this method; Slack side-effects are controlled by in.SlackSync and in.Actor.

type ActorKind

type ActorKind int

ActorKind describes who triggered an UpdateAction call.

const (
	ActorKindSystem ActorKind = iota
	ActorKindSlackUser
)

type ActorRef

type ActorRef struct {
	Kind ActorKind
	ID   string // Slack user ID when Kind == ActorKindSlackUser
}

ActorRef identifies the actor that triggered an update, for change-notification rendering.

type AddActionStepInput

type AddActionStepInput struct {
	WorkspaceID string
	ActionID    int64
	Title       string
	Actor       ActorRef
}

AddActionStepInput is the input for ActionStepUseCase.Add.

type AgentDeps

type AgentDeps struct {
	Repo     interfaces.Repository    // required
	Registry *model.WorkspaceRegistry // required
	LLM      gollem.LLMClient         // required

	// HistoryRepo and TraceRepo are required: the agent session flow persists
	// gollem.History across mentions and writes a trace for each Execute. Pass
	// agentarchive.NewMemoryHistoryRepository / NewMemoryTraceRepository in tests.
	HistoryRepo gollem.HistoryRepository
	TraceRepo   trace.Repository

	// ActionUC is required: the core__create_action tool routes through it so
	// all Action create paths share the same usecase implementation.
	// ActionStepUC follows the same contract for the core__*_action_step
	// tool family.
	ActionUC     *ActionUseCase
	ActionStepUC *ActionStepUseCase

	// MemoUC backs the Case-scoped memo tools (memo__*) in case-bound mode.
	// Optional: nil means the agent gets no memo tools (e.g. the workspace has
	// not enabled memos, or memos are intentionally withheld).
	MemoUC *MemoUseCase

	// KnowledgeUC backs the workspace-wide knowledge tools. Optional: nil means
	// the agent gets no knowledge tools.
	KnowledgeUC *KnowledgeUseCase

	// TagUC backs the workspace-wide tag tools (and tag existence checks). It is
	// required alongside KnowledgeUC for any knowledge tools to be wired.
	TagUC *TagUseCase

	// CaseUC is required for thread mode: the thread-case orchestrator applies
	// the agent's materialize / close decisions through it so every case
	// mutation funnels through the single CaseUseCase entry point.
	CaseUC *CaseUseCase

	// ThreadcaseBudget overrides the planexec budget for the thread-mode
	// agent. Zero values fall back to DefaultThreadcaseBudget.
	ThreadcaseBudget planexec.BudgetConfig

	// Optional Slack tool clients. SlackService is the Bot-token client;
	// SlackSearch and SlackRetriever sit on the User OAuth Token.
	SlackService   slack.Service
	SlackSearch    slacktool.SearchService
	SlackRetriever slacktool.MessageRetriever

	// Optional integrations.
	NotionTool     notiontool.Client
	GitHubClient   *githubtool.Client
	WebFetchClient *webfetch.Client
	EmbedClient    interfaces.EmbedClient

	// JiraTools carries the already-expanded Jira read tools (see
	// pkg/agent/tool/jira / agent.CommonDeps.JiraTools for why this is a
	// plain tool slice rather than a client type). nil/empty means Jira is
	// not configured.
	JiraTools []gollem.Tool
}

AgentDeps groups the dependencies AgentUseCase needs. Required fields are marked below; optional ones can be left zero to disable the corresponding tool or behaviour.

SlackRetriever, when supplied, switches slack__get_messages to a User-token- backed read path so public channels can be fetched without bot membership.

type AgentUseCase

type AgentUseCase struct {
	// contains filtered or unexported fields
}

AgentUseCase is the Slack-side orchestrator for case-bound mention turns. It resolves the request (bot user id, case lookup, conversation snapshot, etc.) and hands off to the casebound runtime (pkg/usecase/agent/casebound) which owns gollem invocation, system prompt assembly, and the turn lock lifecycle.

func NewAgentUseCase

func NewAgentUseCase(deps AgentDeps) *AgentUseCase

NewAgentUseCase creates a new AgentUseCase from a deps bundle. See AgentDeps.

func (*AgentUseCase) HandleAgentMention

func (uc *AgentUseCase) HandleAgentMention(ctx context.Context, msg *slackmodel.Message) error

HandleAgentMention processes an app_mention event and responds with an AI agent

func (*AgentUseCase) HandleSessionInfoRequest

func (uc *AgentUseCase) HandleSessionInfoRequest(ctx context.Context, triggerID, sessionID string) error

HandleSessionInfoRequest opens a modal displaying the session ID

func (*AgentUseCase) HandleThreadCaseCreation

func (uc *AgentUseCase) HandleThreadCaseCreation(ctx context.Context, msg *slackmodel.Message, entry *model.WorkspaceEntry) error

HandleThreadCaseCreation processes a channel-root post (by a human or an integration bot) in a thread-mode monitored channel — the ONLY trigger that initiates case creation. For a bot-relayed post the reporter is best-effort resolved from the first Slack user mention in the body, and stays empty when none is present (thread-mode cases may have no reporter). It does NOT create a case immediately: it runs the initialization (create) agent, which investigates, may ask the user, and only commits a validated case once it is confident. On success it posts a Block Kit summary; on a question it posts an interactive form and waits; on fallback it posts a "couldn't conclude" message. Resuming after a question is driven by the question form's Submit interaction (HandleThreadCaseQuestionSubmit) — a free-text reply or mention in the not-yet-a-case thread is intentionally ignored.

func (*AgentUseCase) HandleThreadCaseMention

func (uc *AgentUseCase) HandleThreadCaseMention(ctx context.Context, msg *slackmodel.Message, entry *model.WorkspaceEntry, foundCase *model.Case) error

HandleThreadCaseMention processes an app_mention inside a thread-mode case thread: it runs the investigation agent and applies the resulting decision (respond / materialize / close).

func (*AgentUseCase) HandleThreadCaseMentionCreation

func (uc *AgentUseCase) HandleThreadCaseMentionCreation(ctx context.Context, msg *slackmodel.Message, entry *model.WorkspaceEntry) error

HandleThreadCaseMentionCreation starts (or resumes) a thread-mode Case from an @mention in a monitored channel that has no Case yet. It is the mention-trigger counterpart of HandleThreadCaseCreation: the mention — at the channel root or inside a case-less thread — is the creation trigger, and the mention text is folded into the create agent's seed. The bot-authored / accept_bot gate is applied by the caller (handleThreadModeEvent) before this runs.

The first mention on a thread seeds a fresh creation turn; a channel-root mention seeds the mention text alone (like instant), while an in-thread mention seeds the whole thread (root + replies + this mention). A follow-up mention while the thread is still not a Case resumes the in-flight session, superseding any pending question with the new intent (mirrors ResumeThreadCaseCreation). Per-thread serialization comes from the turn lock keyed on (channel, threadTS); TriggerTS is the mention's own TS so a re-delivered mention dedups without a follow-up being mistaken for a retry.

func (*AgentUseCase) HandleThreadCaseQuestionSubmit

func (uc *AgentUseCase) HandleThreadCaseQuestionSubmit(ctx context.Context, callback *goslack.InteractionCallback, action *goslack.BlockAction) error

HandleThreadCaseQuestionSubmit is the Submit-button entry point for the thread-mode case-initialization question form. It validates the answer against the pending snapshot, swaps the form into a read-only "answered" record, clears the pending question, and resumes the create agent with the formatted answers as the next-turn input. Missing answers re-render the form with an inline error.

func (*AgentUseCase) HandleWorkspaceAgentMention added in v0.2.0

func (uc *AgentUseCase) HandleWorkspaceAgentMention(ctx context.Context, msg *slackmodel.Message, entry *model.WorkspaceEntry) error

HandleWorkspaceAgentMention processes an app_mention that should run the workspace-level cross-case agent (wsagent) on behalf of the mentioning user, then posts the reply into the mention's thread. The session is case-less (CaseID == 0) and tagged SessionKindWorkspaceAgent: the agent operates across every case the user can access, not one bound case.

Both hosts of the workspace agent funnel through here:

  • channel mode — a mention in the configured [slack] workspace_channel.
  • thread mode — a channel-root mention in the monitored channel (with trigger = "mention"), plus every follow-up mention inside the thread it opened. Those threads never become Cases; the Session's Kind is what the dispatcher reads to keep them out of the case-creation path.

func (*AgentUseCase) ResumeThreadCaseCreation

func (uc *AgentUseCase) ResumeThreadCaseCreation(ctx context.Context, msg *slackmodel.Message, entry *model.WorkspaceEntry) error

ResumeThreadCaseCreation continues the initialization (create) agent on a thread that has no case yet, with msg as the latest user intent; the conversation history (keyed on Session.ID) carries the prior turn's investigation and question. In production this is reached only through the offline eval harness — the live Slack flow resumes a deferred question via the question form's Submit interaction, and free-text replies / mentions in a not-yet-a-case thread are ignored (see handleThreadModeEvent).

type AssistDeps

type AssistDeps struct {
	Repo     interfaces.Repository    // required
	Registry *model.WorkspaceRegistry // required
	LLM      gollem.LLMClient         // required

	// ActionUC routes core__create_action through the unified usecase entry
	// point so assist-driven creates trigger the same Slack post and event
	// records as GraphQL/Slack-modal creates. Required.
	ActionUC *ActionUseCase

	// CaseUC backs the case_ref read tools
	// (core__search_referenceable_cases / core__get_referenceable_cases).
	// Optional: nil disables those tools (no case_ref fields configured).
	CaseUC *CaseUseCase

	// Optional Slack tool clients. SlackService is the Bot-token client;
	// SlackSearch and SlackRetriever sit on the User OAuth Token.
	SlackService   slack.Service
	SlackSearch    slacktool.SearchService
	SlackRetriever slacktool.MessageRetriever

	// Optional integrations.
	NotionTool     notiontool.Client
	GitHubClient   *githubtool.Client
	WebFetchClient *webfetch.Client
	EmbedClient    interfaces.EmbedClient

	// JiraTools carries the already-expanded Jira read tools (see
	// pkg/agent/tool/jira). Unlike NotionTool/GitHubClient/WebFetchClient
	// this is not a client type: the Jira integration is a gollem.ToolSet
	// with no exported ToolSet-to-[]Tool helper, so config.Jira.Configure
	// expands it once at startup and hands the result through as a plain
	// tool slice. nil/empty means Jira is not configured.
	JiraTools []gollem.Tool
}

AssistDeps groups the dependencies AssistUseCase needs. Required fields are marked below; the rest can be left zero to disable the corresponding tool or behaviour.

SlackRetriever, when supplied, lets slack__get_messages read public channels without bot membership via the User token (Slack contract: only user tokens can access public channels they are not in).

type AssistOption

type AssistOption struct {
	WorkspaceID  string
	LogCount     int
	MessageCount int
}

AssistOption holds options for the assist command

type AssistUseCase

type AssistUseCase struct {
	// contains filtered or unexported fields
}

AssistUseCase handles periodic AI-powered case assistance

func NewAssistUseCase

func NewAssistUseCase(deps AssistDeps) *AssistUseCase

NewAssistUseCase creates a new AssistUseCase from a deps bundle. See AssistDeps.

func (*AssistUseCase) RunAssist

func (uc *AssistUseCase) RunAssist(ctx context.Context, opts AssistOption) error

RunAssist iterates all workspaces and open cases, running the assist agent for each

type AuthOption

type AuthOption func(*AuthUseCase)

AuthOption is a functional option for AuthUseCase

func WithBotToken

func WithBotToken(botToken string) AuthOption

WithBotToken sets the Slack bot token

func WithTeamID

func WithTeamID(teamID string) AuthOption

WithTeamID sets the Slack team ID

type AuthUseCase

type AuthUseCase struct {
	// contains filtered or unexported fields
}

func NewAuthUseCase

func NewAuthUseCase(repo interfaces.Repository, clientID, clientSecret, callbackURL string, options ...AuthOption) *AuthUseCase

func (*AuthUseCase) GetAuthURL

func (uc *AuthUseCase) GetAuthURL(state string) string

GetAuthURL returns the URL for Slack OAuth

func (*AuthUseCase) HandleCallback

func (uc *AuthUseCase) HandleCallback(ctx context.Context, code string) (*auth.Token, error)

HandleCallback processes the OAuth callback

func (*AuthUseCase) IsNoAuthn

func (uc *AuthUseCase) IsNoAuthn() bool

IsNoAuthn returns false for regular AuthUseCase

func (*AuthUseCase) Logout

func (uc *AuthUseCase) Logout(ctx context.Context, tokenID auth.TokenID) error

Logout deletes the token

func (*AuthUseCase) ValidateToken

func (uc *AuthUseCase) ValidateToken(ctx context.Context, tokenID auth.TokenID, tokenSecret auth.TokenSecret) (*auth.Token, error)

ValidateToken validates the token and returns user info

type AuthUseCaseInterface

type AuthUseCaseInterface interface {
	GetAuthURL(state string) string
	HandleCallback(ctx context.Context, code string) (*auth.Token, error)
	ValidateToken(ctx context.Context, tokenID auth.TokenID, tokenSecret auth.TokenSecret) (*auth.Token, error)
	Logout(ctx context.Context, tokenID auth.TokenID) error
	IsNoAuthn() bool // Added to identify NoAuthnUseCase
}

AuthUseCaseInterface defines the interface for authentication use cases

type CaseEventPublisher

type CaseEventPublisher interface {
	PublishCaseLifecycle(ctx context.Context, workspaceID string, c *model.Case, lifecycle model.CaseLifecycle, actorUserID string)
}

CaseEventPublisher is the narrow surface of pkg/usecase/job.UseCase that CaseUseCase calls into after a lifecycle transition. The interface is defined here so this package does not import pkg/usecase/job (which would create a cycle: job → usecase → job).

type CaseUpdate

type CaseUpdate struct {
	Title       *string
	Description *string
	// IsTest, when non-nil, sets the test flag; nil preserves the stored value.
	// A pointer is required to distinguish "leave unchanged" from "set false".
	IsTest *bool
	// nil means "preserve all stored field values". A non-nil map merges its
	// entries on top of the existing values (callers cannot remove individual
	// entries via this API).
	Fields map[string]model.FieldValue
}

CaseUpdate represents a partial update to a Case. Each pointer/slice is nil-vs-set: nil means "preserve the existing value", a non-nil pointer means "set to this value (including empty string)". For Fields the nil case preserves all stored field values; a non-nil map merges the supplied entries on top of the existing ones (entries are not removed individually — clients should send the empty value to clear a field if needed).

Assignees are intentionally NOT part of this patch. They are mutated only through the dedicated AssignCase / UnassignCase delta operations, so every entry point shares one race-free path and a full-list replace can never silently clobber a concurrent edit.

type CaseUseCase

type CaseUseCase struct {
	// contains filtered or unexported fields
}

func NewCaseUseCase

func NewCaseUseCase(repo interfaces.Repository, registry *model.WorkspaceRegistry, slackService slack.Service, slackAdminService slack.AdminService, baseURL string) *CaseUseCase

func (*CaseUseCase) AssignCase

func (uc *CaseUseCase) AssignCase(ctx context.Context, workspaceID string, id int64, userIDs []string) (*model.Case, error)

AssignCase atomically adds the given Slack user IDs to the case's assignee set. Unlike UpdateCase — which replaces the whole assignee list and therefore loses a concurrent edit inside its read-modify-write window — the add is applied as a transactional set union in the repository, so two simultaneous "assign me" actions both land. IDs already assigned are ignored. New assignees must resolve to known Slack users. An empty userIDs slice is a no-op that returns the case unchanged.

func (*CaseUseCase) CaseURL

func (uc *CaseUseCase) CaseURL(workspaceID string, caseID int64) string

CaseURL returns the web-UI URL for a specific case detail page, or an empty string when no baseURL has been configured.

func (*CaseUseCase) CloseCase

func (uc *CaseUseCase) CloseCase(ctx context.Context, workspaceID string, id int64) (*model.Case, error)

func (*CaseUseCase) CreateCase

func (uc *CaseUseCase) CreateCase(ctx context.Context, workspaceID string, title, description string, assigneeIDs []string, fieldValues map[string]model.FieldValue, isPrivate bool, isTest bool, sourceTeamID string, requestKey string) (*model.Case, error)

CreateCase persists a brand-new case in status=OPEN and runs the mode- appropriate activation (dedicated Slack channel in channel mode, monitored- channel thread in thread mode). It is the public entry point used by the createCase mutation and the slash-command "submit" flow. The mode decision is delegated to the shared funnel openInWorkspaceMode so every OPEN-case path honours the same invariant.

func (*CaseUseCase) CreateDraft

func (uc *CaseUseCase) CreateDraft(ctx context.Context, workspaceID string, title, description string, assigneeIDs []string, fieldValues map[string]model.FieldValue, isPrivate bool, isTest bool) (*model.Case, error)

CreateDraft persists a case in status=DRAFT — i.e. an "in-progress" entry saved from the Slack creation modal's Save as Draft button. None of the activation side effects (Slack channel, invites, welcome, etc.) run; those fire only when the draft is later promoted via SubmitDraft.

The reporter (auth-context Slack user) becomes the draft owner; the returned case carries the assigned ID so the caller can echo it back to the user.

func (*CaseUseCase) DeleteCase

func (uc *CaseUseCase) DeleteCase(ctx context.Context, workspaceID string, id int64) error

func (*CaseUseCase) DiscardDraft

func (uc *CaseUseCase) DiscardDraft(ctx context.Context, workspaceID string, id int64) error

DiscardDraft permanently deletes a draft. Public drafts are team-wide shared so any workspace member may discard one; private drafts are hidden from non-reporters at the GetDraft layer, which naturally keeps them owner-only. Non-draft cases are rejected so callers cannot pivot this method into a "delete any case" shortcut.

func (*CaseUseCase) GetActionStatusSet

func (uc *CaseUseCase) GetActionStatusSet(workspaceID string) *model.ActionStatusSet

GetActionStatusSet returns the resolved ActionStatusSet for the workspace, falling back to the legacy default when the workspace is unknown or has no custom configuration. This is the canonical accessor for any layer that needs to render or validate action statuses outside ActionUseCase.

func (*CaseUseCase) GetCase

func (uc *CaseUseCase) GetCase(ctx context.Context, workspaceID string, id int64) (*model.Case, error)

func (*CaseUseCase) GetCaseStatusSet

func (uc *CaseUseCase) GetCaseStatusSet(workspaceID string) *model.ActionStatusSet

GetCaseStatusSet returns the configurable Case status set (the Kanban columns) for a thread-mode workspace, or nil for channel-mode workspaces.

func (*CaseUseCase) GetCases

func (uc *CaseUseCase) GetCases(ctx context.Context, workspaceID string, ids []int64) ([]*model.Case, error)

GetCases retrieves multiple cases by ID in a single batch, applying the same per-case access control as GetCase. It fetches all requested cases with one repository batch call (repo.Case().GetByIDs) to avoid N+1 round-trips, then:

  • omits cases that do not exist;
  • omits private drafts the caller did not author (their existence must not leak);
  • RestrictCases non-draft private cases the caller cannot access.

Results preserve the order of ids; duplicate ids yield duplicate entries.

func (*CaseUseCase) GetDraft

func (uc *CaseUseCase) GetDraft(ctx context.Context, workspaceID string, id int64) (*model.Case, error)

GetDraft returns a single draft case. Public drafts are visible workspace-wide so any team member can preview (and act on) an in-progress entry; private drafts remain reporter-only (the usual ChannelUserIDs check can't help yet — the draft has no Slack channel). Non-draft cases return ErrCaseNotDraft so callers cannot reuse the draft resolver to peek at submitted cases.

Mutating actions (SubmitDraft, DiscardDraft) reach the draft through this method, so private-draft access control automatically extends to them: a non-reporter cannot even discover a private draft, let alone modify it. Public drafts are deliberately open — the team owns them.

func (*CaseUseCase) GetFieldConfiguration

func (uc *CaseUseCase) GetFieldConfiguration(workspaceID string) *config.FieldSchema

func (*CaseUseCase) GetReferenceableCases

func (uc *CaseUseCase) GetReferenceableCases(ctx context.Context, workspaceID string, ids []int64) ([]*model.Case, error)

GetReferenceableCases batch-fetches the full Cases for the given IDs in workspaceID, returning only those that are referenceable (non-private, non-draft, existing). IDs that are missing / private / draft are omitted, so the caller can diff the input against the result to report them. Results preserve the input ID order. This backs the agent's detail-fetch tool.

func (*CaseUseCase) ListCases

func (uc *CaseUseCase) ListCases(ctx context.Context, workspaceID string, status *types.CaseStatus) ([]*model.Case, error)

func (*CaseUseCase) ListDrafts

func (uc *CaseUseCase) ListDrafts(ctx context.Context, workspaceID string) ([]*model.Case, error)

ListDrafts returns every draft case in the workspace. Drafts are workspace-wide so any team member can pick one up; private drafts are the exception and remain visible only to their reporter (a draft has no Slack channel yet, so the usual IsCaseAccessible check via ChannelUserIDs would lock everyone out — we use ReporterID instead).

func (*CaseUseCase) ListReferenceableCases

func (uc *CaseUseCase) ListReferenceableCases(ctx context.Context, workspaceID, query string, limit int) ([]model.CaseRef, error)

ListReferenceableCases returns the non-private, non-draft Cases of workspaceID as case_ref candidates. Unlike ListCases, private Cases are dropped entirely (never RestrictCase'd) so a picker cannot even reveal that a private Case exists — the project rule that private Cases are not referenceable. With an empty query, OPEN cases sort first, then by most recently updated. With a query, results match the Case title (substring, case-insensitive) or the Case ID ("#42" / "42"), sorted by most recently updated. At most limit (clamped to referenceableCasesLimit) rows are returned. DRAFT cases are excluded because List excludes them by default.

func (*CaseUseCase) MaterializeThreadCase

func (uc *CaseUseCase) MaterializeThreadCase(ctx context.Context, workspaceID string, id int64, title, description string, fieldValues map[string]model.FieldValue) (*model.Case, error)

MaterializeThreadCase applies the LLM-materialized title / description / custom field values onto a thread-mode Case. Empty title / description are ignored (the placeholder set at creation is kept). Field values are type-checked via the workspace validator before write.

func (*CaseUseCase) ReferenceWorkspaceForField

func (uc *CaseUseCase) ReferenceWorkspaceForField(workspaceID, fieldID string) (string, error)

ReferenceWorkspaceForField returns the reference_workspace configured for the case_ref field fieldID in workspaceID. It errors when the field is unknown or is not a case_ref type, so an agent tool that was handed a wrong field id fails loudly instead of querying the wrong workspace.

func (*CaseUseCase) RenderCaseFieldValues

func (uc *CaseUseCase) RenderCaseFieldValues(ctx context.Context, workspaceID string, fieldValues map[string]model.FieldValue) (map[string]any, error)

RenderCaseFieldValues flattens a Case's field values into a plain map for an agent tool response. Non-reference fields render their raw stored value (as the existing tool responses do). Case-reference fields are resolved one level — each referenced Case ID becomes {id, title, status} — using the field's configured reference_workspace. An ID that no longer resolves (the referenced Case was deleted, made private, or turned into a draft after it was stored) renders as {id, available:false}; this read-time drift is the only place an "unavailable" reference can appear, since writes are gated by verifyCaseRefsExist. A repository lookup failure is propagated, never swallowed.

func (*CaseUseCase) ReopenCase

func (uc *CaseUseCase) ReopenCase(ctx context.Context, workspaceID string, id int64) (*model.Case, error)

func (*CaseUseCase) ResolveCaseRefs

func (uc *CaseUseCase) ResolveCaseRefs(ctx context.Context, workspaceID string, ids []int64) ([]model.CaseRef, error)

ResolveCaseRefs resolves the given Case IDs in workspaceID to their CaseRef summaries, dropping any ID that is missing, private, or a draft (the caller renders those as "unavailable"). Used to label existing case_ref values for display. A single batch lookup keeps it N+1-safe.

func (*CaseUseCase) SetEventPublisher

func (uc *CaseUseCase) SetEventPublisher(p CaseEventPublisher)

SetEventPublisher wires the lifecycle event publisher. Called once at startup after the job dispatch UseCase has been constructed. nil is allowed (Job dispatch effectively disabled).

func (*CaseUseCase) SubmitDraft

func (uc *CaseUseCase) SubmitDraft(ctx context.Context, workspaceID string, id int64, patch *CaseUpdate) (*model.Case, error)

SubmitDraft promotes a draft case to OPEN and triggers the same activation side effects (Slack channel, invites, welcome, etc.) as a fresh CreateCase. The optional `patch` carries last-minute edits the caller wants to apply atomically before the promotion — passing them to this single usecase method (rather than separate UpdateCase + SubmitDraft calls from the controller) keeps the "save final edits and submit" business operation atomic: required-field validation, channel creation, and invites all see the same set of values, and a failure path leaves the draft consistent.

If activation fails the draft is kept in DRAFT so the user can retry without losing the saved entry.

func (*CaseUseCase) SyncCaseChannelUsers

func (uc *CaseUseCase) SyncCaseChannelUsers(ctx context.Context, workspaceID string, caseID int64) (*model.Case, error)

SyncCaseChannelUsers synchronizes channel members from Slack API to the case

func (*CaseUseCase) UnassignCase

func (uc *CaseUseCase) UnassignCase(ctx context.Context, workspaceID string, id int64, userIDs []string) (*model.Case, error)

UnassignCase atomically removes the given Slack user IDs from the case's assignee set. IDs not currently assigned are ignored. Removal needs no user existence check (a since-deleted user must still be removable). An empty userIDs slice is a no-op that returns the case unchanged.

func (*CaseUseCase) UpdateAgentSettings

func (uc *CaseUseCase) UpdateAgentSettings(ctx context.Context, workspaceID string, caseID int64, additionalPrompt string, enabledSourceIDs []model.SourceID) (*model.Case, error)

UpdateAgentSettings replaces the Case-specific agent additional prompt and the AgentSourceIDs whitelist. enabledSourceIDs == nil or empty resets the selection to "use every Source". Non-empty IDs are validated against the Workspace's Source list — any unknown ID makes the whole update fail with ErrInvalidArgument (we never silently drop an ID the caller meant to keep). Order is preserved exactly as supplied so the UI selection round-trips unchanged.

func (*CaseUseCase) UpdateCase

func (uc *CaseUseCase) UpdateCase(ctx context.Context, workspaceID string, id int64, patch CaseUpdate) (*model.Case, error)

func (*CaseUseCase) UpdateCaseStatus

func (uc *CaseUseCase) UpdateCaseStatus(ctx context.Context, workspaceID string, id int64, boardStatus string) (*model.Case, error)

UpdateCaseStatus sets the configurable board status of a thread-mode Case and synchronises the lifecycle Status (a closed board status closes the case). It is the single entry point for both the Kanban drag-and-drop and the agent's `close` decision; CaseLifecycleClosed is published only on the open→closed edge so Jobs fire once.

type CreateGitHubSourceInput

type CreateGitHubSourceInput struct {
	Name         string
	Description  string
	Repositories []string // "owner/repo" or GitHub URL format
	Enabled      bool
}

CreateGitHubSourceInput represents input for creating a GitHub source

type CreateKnowledgeInput

type CreateKnowledgeInput struct {
	Title  string
	Claim  string
	TagIDs []model.TagID
}

CreateKnowledgeInput is the domain-level input for creating a knowledge entry.

func (CreateKnowledgeInput) Validate

func (input CreateKnowledgeInput) Validate() error

Validate enforces input invariants at the entry point: a title and at least one tag id are required, and the claim must be within the length limit. Tag existence is verified separately against the repository in CreateKnowledge.

type CreateMemoInput

type CreateMemoInput struct {
	CaseID      int64
	Title       string
	FieldValues map[string]model.FieldValue
}

CreateMemoInput is the unified input for MemoUseCase.CreateMemo.

type CreateNotionDBSourceInput

type CreateNotionDBSourceInput struct {
	Name        string
	Description string
	DatabaseID  string
	Enabled     bool
}

CreateNotionDBSourceInput represents input for creating a Notion DB source

type CreateNotionPageSourceInput

type CreateNotionPageSourceInput struct {
	Name        string
	Description string
	PageID      string
	Enabled     bool
	Recursive   bool
	MaxDepth    int
}

CreateNotionPageSourceInput represents input for creating a Notion Page source

type CreateSlackSourceInput

type CreateSlackSourceInput struct {
	Name        string
	Description string
	ChannelIDs  []string
	Enabled     bool
}

CreateSlackSourceInput represents input for creating a Slack source

type DashboardUseCase

type DashboardUseCase struct {
	// contains filtered or unexported fields
}

DashboardUseCase serves the login home dashboard: cross-workspace aggregation of the caller's own open Cases and incomplete Actions, favorite-workspace preferences, and the LLM-generated greeting. It is a dedicated usecase because it spans Case, Action, and UserPreference/HomeMessage repositories plus the workspace registry.

func (*DashboardUseCase) GenerateHomeMessage

func (uc *DashboardUseCase) GenerateHomeMessage(ctx context.Context, clientTime time.Time, lang string) (string, error)

GenerateHomeMessage returns the home greeting for the caller, reusing the most recent stored message when it is still fresh (same language, within the window) and generating+appending a new one otherwise. Returns "" (no error) when no greeting LLM is configured.

func (*DashboardUseCase) GetFavoriteWorkspaces

func (uc *DashboardUseCase) GetFavoriteWorkspaces(ctx context.Context) ([]string, error)

GetFavoriteWorkspaces returns the caller's favorite workspace IDs, filtered to those that still exist in the registry.

func (*DashboardUseCase) ListMyDueActions

func (uc *DashboardUseCase) ListMyDueActions(ctx context.Context) ([]*model.MyDueAction, error)

ListMyDueActions returns, across every workspace, the caller's incomplete Actions on accessible open Cases, ordered by due date (overdue first, no due date last).

func (*DashboardUseCase) ListMyOpenCases

func (uc *DashboardUseCase) ListMyOpenCases(ctx context.Context) ([]*model.MyOpenCase, error)

ListMyOpenCases returns, across every workspace, the open Cases the caller is assigned to and may access, newest activity / stalled first.

func (*DashboardUseCase) SetFavoriteWorkspaces

func (uc *DashboardUseCase) SetFavoriteWorkspaces(ctx context.Context, workspaceIDs []string) ([]string, error)

SetFavoriteWorkspaces replaces the caller's favorite workspace list wholesale. Unknown workspace IDs are rejected; duplicates are removed (order preserved). Returns the stored list.

type DeleteActionStepInput

type DeleteActionStepInput struct {
	WorkspaceID string
	ActionID    int64
	StepID      string
	Actor       ActorRef
}

DeleteActionStepInput is the input for ActionStepUseCase.Delete.

type GitHubRepoValidationResult

type GitHubRepoValidationResult struct {
	Valid                bool
	Owner                string
	Repo                 string
	FullName             string
	Description          string
	IsPrivate            bool
	PullRequestCount     int
	IssueCount           int
	CanFetchPullRequests bool
	CanFetchIssues       bool
	ErrorMessage         string
}

GitHubRepoValidationResult represents the result of GitHub repository validation

type ImportUseCase

type ImportUseCase struct {
	// contains filtered or unexported fields
}

ImportUseCase owns the "YAML → Case/Action" wizard. It delegates the actual Case / Action creation to the existing CaseUseCase / ActionUseCase so business rules (history events, private-case invariants, Slack-suppression because the Case is in DRAFT, etc.) stay in one place.

func NewImportUseCase

func NewImportUseCase(repo interfaces.Repository, registry *model.WorkspaceRegistry, caseUC *CaseUseCase, actionUC *ActionUseCase) *ImportUseCase

NewImportUseCase constructs an ImportUseCase.

func (*ImportUseCase) Create

func (uc *ImportUseCase) Create(ctx context.Context, workspaceID, content, originalFileName string) (*model.ImportSession, error)

Create parses, validates, and persists a new ImportSession in pending state. Caller must be authenticated; CreatorUserID is captured from the auth context.

func (*ImportUseCase) Execute

func (uc *ImportUseCase) Execute(ctx context.Context, workspaceID string, id model.ImportSessionID) (*model.ImportSession, error)

Execute runs the persisted snapshot against the workspace, creating Case (DRAFT) and Action records via the existing usecases. The first failure halts the loop; subsequent items become skipped. No rollback is performed (DRAFTs are harmless to leave around — they have no Slack side effects).

func (*ImportUseCase) Get

func (uc *ImportUseCase) Get(ctx context.Context, workspaceID string, id model.ImportSessionID) (*model.ImportSession, error)

Get returns the session if it exists AND the caller is the creator. Returns ErrImportSessionNotFound otherwise so callers cannot probe other users' session IDs.

type JobRunLogPage

type JobRunLogPage struct {
	Items      []*model.JobRunLog
	NextCursor *string
}

JobRunLogPage is the result of ListLogsByCase. NextCursor is non-nil only when more pages exist; the value is an opaque base64 token that the caller passes back as `after` on the next call.

type JobRunUseCase

type JobRunUseCase struct {
	// contains filtered or unexported fields
}

JobRunUseCase exposes read-only access to Job execution history and the per-Run timeline. It is intentionally narrow: the CaseAgent page and the JobRunLog detail page both rely on these three methods (plus CaseUseCase for the parent-Case access check, which is reused here).

Access control follows the same Private Case pattern as CaseUseCase: the parent Case is loaded first; if the caller does not belong to its channel, the read is refused with ErrAccessDenied. System contexts without an auth token (background runner, etc.) bypass the check — the same backward-compatibility carveout already used elsewhere.

func NewJobRunUseCase

func NewJobRunUseCase(repo interfaces.Repository, registry *model.WorkspaceRegistry) *JobRunUseCase

NewJobRunUseCase wires the JobRunUseCase. registry may be nil: the jobName field on returned logs falls back to the JobID when no entry is registered (e.g. early bootstrap), and never blocks the call.

func (*JobRunUseCase) GetLog

func (uc *JobRunUseCase) GetLog(ctx context.Context, workspaceID string, caseID int64, runID string) (*model.JobRunLog, error)

GetLog returns a single JobRunLog by RunID. The runID is unique per Run, but the storage layout includes the JobID in the document path, so we resolve the JobID by walking the per-Case JobRun list (small) and probing each one. This avoids a collectionGroup query.

func (*JobRunUseCase) ListCaseJobs

func (uc *JobRunUseCase) ListCaseJobs(ctx context.Context, workspaceID string, caseID int64) ([]*model.Job, error)

ListCaseJobs returns the enabled Job definitions that can fire against the given Case. Definitions come from the in-memory Workspace registry (workspace TOML), never a repository. A scheduled Job is included only while the Case is OPEN — mirroring the ScheduledScanner, which skips DRAFT/CLOSED cases — whereas a case-lifecycle Job is always included.

Access control matches the other JobRun reads: the parent Case is loaded first and a non-member caller is refused with ErrAccessDenied; system contexts without an auth token bypass the check. When the registry is unset (early bootstrap) the result is an empty slice.

func (*JobRunUseCase) ListEvents

func (uc *JobRunUseCase) ListEvents(ctx context.Context, workspaceID string, caseID int64, runID string) ([]*model.JobRunEvent, error)

ListEvents returns every JobRunEvent under (workspace, case, run) in ascending Sequence order. As with GetLog, the JobID is resolved by walking the per-Case JobRun list first.

func (*JobRunUseCase) ListLogsByCase

func (uc *JobRunUseCase) ListLogsByCase(ctx context.Context, workspaceID string, caseID int64, page int, after *string) (*JobRunLogPage, error)

ListLogsByCase returns one page of JobRunLogs for the given Case across every Job that has ever run against it, ordered newest-first.

Pagination semantics: page is the requested size (clamped to [1, JobRunLogPageMaxSize], defaulting to JobRunLogPageDefaultSize on zero/negative). `after` is the opaque cursor from a previous call; nil/empty means "start at the head".

Implementation note: the storage layout is per-Job (logs live under jobRuns/{job}/logs/), so a true cross-job query would need a Firestore collectionGroup (and a composite index). To stay within the project's "no new indexes" rule we fan out to one List per Job in parallel (small N: handful of Jobs per Case) and merge in memory. The cursor is then applied to the merged slice.

func (*JobRunUseCase) ResolveJobName

func (uc *JobRunUseCase) ResolveJobName(ctx context.Context, workspaceID, jobID, eventType string) string

ResolveJobName returns the human-readable Job name from the workspace TOML registry, falling back to the raw JobID when no entry exists. Exposed so resolvers can label runs without re-loading the registry themselves. A mention-triggered run (eventType == EventTypeMention) is not a configured Job — its JobID is an opaque per-turn id — so it resolves to a localized "Mention" label instead.

type KnowledgeUseCase

type KnowledgeUseCase struct {
	// contains filtered or unexported fields
}

KnowledgeUseCase orchestrates workspace-wide shared knowledge operations. Embedding is optional: when no embed client is configured the use case degrades gracefully (no semantic vectors, substring search fallback).

func NewKnowledgeUseCase

func NewKnowledgeUseCase(repo interfaces.Repository, embedClient interfaces.EmbedClient) *KnowledgeUseCase

NewKnowledgeUseCase constructs a KnowledgeUseCase. embedClient may be nil (fail-open: create/update still succeed and search falls back to substring).

func (*KnowledgeUseCase) CreateKnowledge

func (uc *KnowledgeUseCase) CreateKnowledge(ctx context.Context, workspaceID string, input CreateKnowledgeInput) (*model.Knowledge, error)

CreateKnowledge creates a new knowledge entry. The embedding is generated best-effort: a failure or a missing embed client never blocks creation.

func (*KnowledgeUseCase) DeleteKnowledge

func (uc *KnowledgeUseCase) DeleteKnowledge(ctx context.Context, workspaceID string, id model.KnowledgeID) error

DeleteKnowledge removes a knowledge entry.

func (*KnowledgeUseCase) GetKnowledge

func (uc *KnowledgeUseCase) GetKnowledge(ctx context.Context, workspaceID string, id model.KnowledgeID) (*model.Knowledge, error)

GetKnowledge retrieves a knowledge entry by ID.

func (*KnowledgeUseCase) ListKnowledge

func (uc *KnowledgeUseCase) ListKnowledge(ctx context.Context, workspaceID string, opts interfaces.KnowledgeListOptions) ([]*model.Knowledge, error)

ListKnowledge lists knowledge entries with an optional tag AND filter.

func (*KnowledgeUseCase) SearchKnowledge

func (uc *KnowledgeUseCase) SearchKnowledge(ctx context.Context, workspaceID string, input SearchKnowledgeInput) ([]*model.Knowledge, error)

SearchKnowledge ranks entries by semantic similarity to the query. When no embedding is available (no embed client, embedding failure, or candidates without vectors) it falls back to a substring-match score over title + claim.

func (*KnowledgeUseCase) UpdateKnowledge

func (uc *KnowledgeUseCase) UpdateKnowledge(ctx context.Context, workspaceID string, input UpdateKnowledgeInput) (*model.Knowledge, error)

UpdateKnowledge applies a partial update. The embedding is regenerated when the title or claim changes (best-effort).

type MemoConfiguration

type MemoConfiguration struct {
	Enabled     bool
	Description string
	Fields      []config.FieldDefinition
}

MemoConfiguration describes a workspace's memo configuration for the WebUI: the strong definition and the custom field definitions used to render the memo form. Both are empty when the workspace has not enabled memos.

type MemoUseCase

type MemoUseCase struct {
	// contains filtered or unexported fields
}

MemoUseCase orchestrates Case-scoped memo operations. Every write funnels through the same field validation + access control gate so the GraphQL/WebUI path and the agent-tool path enforce identical rules.

func NewMemoUseCase

func NewMemoUseCase(repo interfaces.Repository, registry *model.WorkspaceRegistry) *MemoUseCase

NewMemoUseCase constructs a MemoUseCase.

func (*MemoUseCase) ArchiveMemo

func (uc *MemoUseCase) ArchiveMemo(ctx context.Context, workspaceID string, caseID int64, id model.MemoID) (*model.Memo, error)

ArchiveMemo soft-deletes a memo by setting ArchivedAt. Idempotent: archiving an already-archived memo is a no-op write that returns the current state.

func (*MemoUseCase) CreateMemo

func (uc *MemoUseCase) CreateMemo(ctx context.Context, workspaceID string, in CreateMemoInput) (*model.Memo, error)

CreateMemo validates and persists a new memo within a Case. Field values are fully validated (required fields enforced, unknown field ids rejected).

func (*MemoUseCase) GetMemo

func (uc *MemoUseCase) GetMemo(ctx context.Context, workspaceID string, caseID int64, id model.MemoID) (*model.Memo, error)

GetMemo loads a single memo within a Case. Returns ErrAccessDenied for a private case the (token-bearing) caller cannot access.

func (*MemoUseCase) ListMemosByCase

func (uc *MemoUseCase) ListMemosByCase(ctx context.Context, workspaceID string, caseID int64, scope interfaces.MemoArchiveScope) ([]*model.Memo, error)

ListMemosByCase returns the memos of a Case filtered by scope. For a private case the (token-bearing) caller cannot access, it returns an empty slice rather than an error so list surfaces degrade quietly (read-side restriction).

func (*MemoUseCase) MemoConfiguration

func (uc *MemoUseCase) MemoConfiguration(workspaceID string) (*MemoConfiguration, error)

MemoConfiguration returns the workspace memo configuration for the WebUI. When the workspace has no memo schema it returns a disabled configuration (Enabled=false, empty fields) rather than an error, so the frontend can hide the Memos tab.

func (*MemoUseCase) UnarchiveMemo

func (uc *MemoUseCase) UnarchiveMemo(ctx context.Context, workspaceID string, caseID int64, id model.MemoID) (*model.Memo, error)

UnarchiveMemo restores a soft-deleted memo by clearing ArchivedAt.

func (*MemoUseCase) UpdateMemo

func (uc *MemoUseCase) UpdateMemo(ctx context.Context, workspaceID string, in UpdateMemoInput) (*model.Memo, error)

UpdateMemo applies a partial patch to an existing memo. The patch field values are merged over the stored values and the merged set is validated in full.

type MentionProposalUseCase

type MentionProposalUseCase struct {
	// contains filtered or unexported fields
}

MentionProposalUseCase handles app_mention events that occur in channels NOT bound to an existing Case. It funnels each mention into proposal.UseCase (the open-mode planner / sub-agent runtime), passing a per-mention slackDraftHandler that translates terminal actions and trace updates into Slack messages.

func NewMentionProposalUseCase

func NewMentionProposalUseCase(
	repo interfaces.Repository,
	registry *model.WorkspaceRegistry,
	slackService slacksvc.Service,
	draftUC *proposal.UseCase,
) *MentionProposalUseCase

NewMentionProposalUseCase constructs a MentionProposalUseCase. All dependencies are mandatory; callers that cannot supply a Slack service should refrain from constructing this usecase (and the Slack interaction handler that depends on it) entirely.

func (*MentionProposalUseCase) HandleAppMention

func (uc *MentionProposalUseCase) HandleAppMention(ctx context.Context, ev *slackevents.AppMentionEvent) error

HandleAppMention runs the initial-mention flow: candidate workspace resolution → message collection → draft persistence → planner-driven turn (proposal.UseCase.RunTurn). The slackDraftHandler renders the planner's terminal action (post_message / post_question / materialize) into Slack.

It is the caller's responsibility to ensure the channel is NOT bound to an existing Case (the dispatch in SlackUseCases handles that branch).

func (*MentionProposalUseCase) HandleCancel

HandleCancel marks the draft preview as canceled in place: the existing blocks are kept (so the conversation has a record of what was drafted), every ActionBlock is stripped (workspace selector + buttons live in the same ActionBlock now, both go), and a "Canceled" tail is appended. The underlying draft is deleted from the repository.

func (*MentionProposalUseCase) HandleEdit

HandleEdit opens the dynamic Edit modal for the currently-selected workspace.

func (*MentionProposalUseCase) HandleEditSubmit

func (uc *MentionProposalUseCase) HandleEditSubmit(ctx context.Context, caseUC *CaseUseCase, callback *goslack.InteractionCallback) error

HandleEditSubmit processes the view_submission for the Edit modal.

func (*MentionProposalUseCase) HandleQuestionSubmit

func (uc *MentionProposalUseCase) HandleQuestionSubmit(ctx context.Context, callback *goslack.InteractionCallback, action *goslack.BlockAction) error

HandleQuestionSubmit is the Submit-button entry point for the open-mode question form. It loads the live session, parses the user's selections against the pending question snapshot, validates that every item has an answer, swaps the form message into a read-only "answered" record, and resumes the planner with the formatted answers as the next-turn user input. Validation failures re-render the form with an inline error so the user can fix and resubmit.

func (*MentionProposalUseCase) HandleSelectWorkspace

func (uc *MentionProposalUseCase) HandleSelectWorkspace(ctx context.Context, callback *goslack.InteractionCallback, action *goslack.BlockAction) error

HandleSelectWorkspace runs when the user changes the workspace selector on the preview ephemeral. It re-routes the request through proposal.UseCase.RunTurn with a TriggerWSSwitch trigger; the planner re-materialises against the new workspace's schema using the existing conversation history. The lock-first ordering of F4-3 is preserved by setting InferenceInProgress before the turn starts so concurrent interactions (Submit/Edit/Cancel) refuse.

func (*MentionProposalUseCase) HandleSubmit

func (uc *MentionProposalUseCase) HandleSubmit(ctx context.Context, caseUC *CaseUseCase, callback *goslack.InteractionCallback, action *goslack.BlockAction) error

HandleSubmit creates the Case using the current materialization and posts the completion notification.

func (*MentionProposalUseCase) HandleThreadReply

func (uc *MentionProposalUseCase) HandleThreadReply(ctx context.Context, ev *slackevents.MessageEvent) error

HandleThreadReply runs when the dispatcher's F1-F8 filter chain has decided that a non-mention thread reply should resume the open-mode draft turn. The Session's LastAction is post_question — the planner will read the new user input from history and produce the next action.

type NoAuthnUseCase

type NoAuthnUseCase struct {
	// contains filtered or unexported fields
}

NoAuthnUseCase provides authentication using a specified user (for development/testing)

func NewNoAuthnUseCase

func NewNoAuthnUseCase(repo interfaces.Repository, sub, email, name string) *NoAuthnUseCase

NewNoAuthnUseCase creates a new NoAuthnUseCase instance with specified user info

func (*NoAuthnUseCase) GetAuthURL

func (uc *NoAuthnUseCase) GetAuthURL(state string) string

GetAuthURL returns a dummy URL (should not be called in no-auth mode)

func (*NoAuthnUseCase) HandleCallback

func (uc *NoAuthnUseCase) HandleCallback(ctx context.Context, code string) (*auth.Token, error)

HandleCallback handles OAuth callback (should not be called in no-auth mode)

func (*NoAuthnUseCase) IsNoAuthn

func (uc *NoAuthnUseCase) IsNoAuthn() bool

IsNoAuthn returns true for NoAuthnUseCase

func (*NoAuthnUseCase) Logout

func (uc *NoAuthnUseCase) Logout(ctx context.Context, tokenID auth.TokenID) error

Logout does nothing in no-auth mode

func (*NoAuthnUseCase) ValidateToken

func (uc *NoAuthnUseCase) ValidateToken(ctx context.Context, tokenID auth.TokenID, tokenSecret auth.TokenSecret) (*auth.Token, error)

ValidateToken always returns a token for the specified user

type NotionDBValidationResult

type NotionDBValidationResult struct {
	Valid         bool
	DatabaseTitle string
	DatabaseURL   string
	ErrorMessage  string
}

NotionDBValidationResult represents the result of Notion database validation

type NotionPageValidationResult

type NotionPageValidationResult struct {
	Valid        bool
	PageTitle    string
	PageURL      string
	ErrorMessage string
}

NotionPageValidationResult represents the result of Notion page validation

type OpenIDConfiguration

type OpenIDConfiguration struct {
	Issuer                            string   `json:"issuer"`
	AuthorizationEndpoint             string   `json:"authorization_endpoint"`
	TokenEndpoint                     string   `json:"token_endpoint"`
	UserinfoEndpoint                  string   `json:"userinfo_endpoint"`
	JWKSURI                           string   `json:"jwks_uri"`
	ScopesSupported                   []string `json:"scopes_supported"`
	ResponseTypesSupported            []string `json:"response_types_supported"`
	ResponseModesSupported            []string `json:"response_modes_supported"`
	GrantTypesSupported               []string `json:"grant_types_supported"`
	SubjectTypesSupported             []string `json:"subject_types_supported"`
	IDTokenSigningAlgValuesSupported  []string `json:"id_token_signing_alg_values_supported"`
	ClaimsSupported                   []string `json:"claims_supported"`
	ClaimsParameterSupported          bool     `json:"claims_parameter_supported"`
	RequestParameterSupported         bool     `json:"request_parameter_supported"`
	RequestURIParameterSupported      bool     `json:"request_uri_parameter_supported"`
	TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
}

OpenIDConfiguration represents Slack's OpenID Connect configuration

type Option

type Option func(*UseCases)

func WithAuth

func WithAuth(auth AuthUseCaseInterface) Option

func WithBaseURL

func WithBaseURL(url string) Option

func WithDashboardStaleThreshold

func WithDashboardStaleThreshold(d time.Duration) Option

WithDashboardStaleThreshold sets the age after which an open Case with no update is flagged as stalled on the home dashboard. A non-positive value disables stale detection (Stalled is always false).

func WithEmbedClient

func WithEmbedClient(client interfaces.EmbedClient) Option

WithEmbedClient sets the embedding client. The Memory / Knowledge similarity search consumers were demolished pending redesign, so the client currently has no production reader; the option is preserved so the upcoming redesign can drop similarity-search features back in without rewiring the CLI / usecase boundary. Configured separately from the chat completion LLM so it can target Gemini regardless of provider.

func WithGitHubService

func WithGitHubService(c *github.Client) Option

func WithHistoryRepository

func WithHistoryRepository(repo gollem.HistoryRepository) Option

WithHistoryRepository sets the gollem.HistoryRepository used by the agent session flow to persist conversation history across mentions.

func WithHomeMessageLLMClient

func WithHomeMessageLLMClient(client gollem.LLMClient) Option

WithHomeMessageLLMClient sets a dedicated LLM client for the home greeting. When omitted, the shared chat LLM client (WithLLMClient) is used if present; when neither is set, the greeting is disabled (GenerateHomeMessage returns "").

func WithJiraTools

func WithJiraTools(tools []gollem.Tool) Option

WithJiraTools configures the already-expanded Jira read tools (see pkg/agent/tool/jira). Unlike WithGitHubService this takes a plain tool slice, not a client: gollem exposes no exported helper to turn a gollem.ToolSet into []gollem.Tool, so the caller (pkg/cli/config.Jira. Configure) expands it once at startup before passing it in here.

func WithLLMClient

func WithLLMClient(client gollem.LLMClient) Option

func WithNotificationSlotDuration

func WithNotificationSlotDuration(d time.Duration) Option

WithNotificationSlotDuration sets the rolling window length used to aggregate Slack channel-side change notifications into a single editable message. Pass 0 (the default) to disable aggregation, restoring the legacy per-event reply_broadcast path.

func WithNotion

func WithNotion(svc notion.Service) Option

func WithNotionToolClient

func WithNotionToolClient(c notiontool.Client) Option

WithNotionToolClient configures the agent-tool Notion client. When set, the agent gains the notion__search and notion__get_page tools.

func WithSlackAdminService

func WithSlackAdminService(svc slack.AdminService) Option

func WithSlackMessageRetriever

func WithSlackMessageRetriever(svc slacktool.MessageRetriever) Option

WithSlackMessageRetriever configures the Slack User-token-backed message retriever used by slack__get_messages. When set, conversations.replies / conversations.history are called with the User token, which allows reading public channels without bot membership. Requires the underlying User OAuth Token to have the channels:history scope. nil keeps the existing Bot-token path (which returns not_in_channel when the bot is not a channel member).

func WithSlackSearchService

func WithSlackSearchService(svc slacktool.SearchService) Option

WithSlackSearchService configures the Slack User-token-backed search client. When set, the agent gains the slack__search_messages tool. Requires the underlying User OAuth Token to have the search:read scope.

func WithSlackService

func WithSlackService(svc slack.Service) Option

func WithTraceRepository

func WithTraceRepository(repo trace.Repository) Option

WithTraceRepository sets the trace.Repository used by the agent session flow to persist execution traces.

func WithWebFetch

func WithWebFetch(cfg webfetch.ClientConfig) Option

WithWebFetch configures the agent webfetch tool's HTTP-side settings. The shared LLM client (used for injection screening) is injected in New, so the tool is built only when both these settings and an LLM client are present.

func WithWorkspaceGroups

func WithWorkspaceGroups(r *model.WorkspaceGroupRegistry) Option

WithWorkspaceGroups configures the deployment-wide workspace group registry (loaded from --global-config). Optional: when omitted, New defaults to an empty registry so WorkspaceGroups() never returns nil.

type RenameActionStepInput

type RenameActionStepInput struct {
	WorkspaceID string
	ActionID    int64
	StepID      string
	Title       string
	Actor       ActorRef
}

RenameActionStepInput is the input for ActionStepUseCase.Rename.

type SearchKnowledgeInput

type SearchKnowledgeInput struct {
	// Query is the natural-language search text. Empty returns the (optionally
	// tag-filtered) list ordered by CreatedAt.
	Query string
	// TagIDs applies an AND pre-filter before ranking.
	TagIDs []model.TagID
	// Limit caps the number of returned entries. Zero or negative means no cap;
	// the caller (resolver / tool) supplies the default.
	Limit int
}

SearchKnowledgeInput controls a semantic search query.

type SetActionStepDoneInput

type SetActionStepDoneInput struct {
	WorkspaceID string
	ActionID    int64
	StepID      string
	Done        bool
	Actor       ActorRef
}

SetActionStepDoneInput is the input for ActionStepUseCase.SetDone.

type SlackChannelInfo

type SlackChannelInfo struct {
	ID   string
	Name string
}

SlackChannelInfo represents channel information returned from the API

type SlackIDToken

type SlackIDToken struct {
	Sub   string `json:"sub"`
	Email string `json:"email"`
	Name  string `json:"name"`
}

SlackIDToken represents the decoded ID token from Slack

type SlackSyncMode

type SlackSyncMode int

SlackSyncMode controls how UpdateAction interacts with Slack.

const (
	// SlackSyncFull updates the existing Slack message and posts a thread
	// notification for visible field changes (default).
	SlackSyncFull SlackSyncMode = iota
	// SlackSyncMessageOnly only refreshes the existing Slack message; no
	// thread notification is posted.
	SlackSyncMessageOnly
	// SlackSyncSkip leaves Slack untouched.
	SlackSyncSkip
)

type SlackTokenResponse

type SlackTokenResponse struct {
	OK          bool   `json:"ok"`
	AccessToken string `json:"access_token"`
	TokenType   string `json:"token_type"`
	Scope       string `json:"scope"`
	BotUserID   string `json:"bot_user_id"`
	AppID       string `json:"app_id"`
	Team        struct {
		Name string `json:"name"`
		ID   string `json:"id"`
	} `json:"team"`
	Enterprise interface{} `json:"enterprise"`
	AuthedUser struct {
		ID          string `json:"id"`
		Scope       string `json:"scope"`
		AccessToken string `json:"access_token"`
		TokenType   string `json:"token_type"`
	} `json:"authed_user"`
	IDToken string `json:"id_token"`
	Error   string `json:"error"`
}

SlackTokenResponse represents the response from Slack token exchange

type SlackUseCases

type SlackUseCases struct {
	// contains filtered or unexported fields
}

SlackUseCases handles Slack-related business logic

func NewSlackUseCases

func NewSlackUseCases(repo interfaces.Repository, registry *model.WorkspaceRegistry, agent *AgentUseCase, mentionProposal *MentionProposalUseCase, slackService slacksvc.Service) *SlackUseCases

NewSlackUseCases creates a new SlackUseCases instance. agent and mentionProposal are mandatory — Slack mention dispatch requires both.

func (*SlackUseCases) CleanupOldMessages

func (uc *SlackUseCases) CleanupOldMessages(ctx context.Context, before time.Time) error

CleanupOldMessages deletes messages older than the specified time

func (*SlackUseCases) HandleActionCreationSubmit

func (uc *SlackUseCases) HandleActionCreationSubmit(ctx context.Context, actionUC *ActionUseCase, callback *slack.InteractionCallback) error

HandleActionCreationSubmit processes the action creation modal submission. All side-effects (Slack notification post, ActionEvent record, etc.) live inside ActionUseCase.CreateAction; this handler only translates the view state into the unified usecase input.

func (*SlackUseCases) HandleCaseCreationSubmit

func (uc *SlackUseCases) HandleCaseCreationSubmit(ctx context.Context, caseUC *CaseUseCase, callback *slack.InteractionCallback) error

HandleCaseCreationSubmit processes the case creation modal submission (view_submission path — i.e. the footer Submit button or Enter on an input). The Options checkbox group governs two flags: "private" gates channel-member-only access; "draft" diverts the request to CaseUseCase.CreateDraft so the entry is persisted in status=DRAFT without creating a Slack channel until the user later submits the draft from the web UI.

func (*SlackUseCases) HandleCaseEditSubmit

func (uc *SlackUseCases) HandleCaseEditSubmit(ctx context.Context, caseUC *CaseUseCase, callback *slack.InteractionCallback) error

HandleCaseEditSubmit processes the case edit modal submission. It updates the case using CaseUseCase and posts a confirmation message.

func (*SlackUseCases) HandleCommandChoiceSubmit

func (uc *SlackUseCases) HandleCommandChoiceSubmit(ctx context.Context, callback *slack.InteractionCallback) (*slack.ModalViewRequest, error)

HandleCommandChoiceSubmit processes the choice modal submission. It returns the next view (case edit modal or action creation modal) so the controller can respond with response_action: update.

func (*SlackUseCases) HandleSaveAsDraftClick

func (uc *SlackUseCases) HandleSaveAsDraftClick(ctx context.Context, caseUC *CaseUseCase, callback *slack.InteractionCallback) error

HandleSaveAsDraftClick handles the "Save as Draft" button click that originates from the Case creation modal. The block_actions interaction carries the in-flight view state (title, description, custom fields, privacy flag) plus the modal's private_metadata (workspace ID, channel, source team). We translate that state into a CreateDraft call so the half-written entry is persisted in CaseStatus.DRAFT — the user can later return to it from the Web Drafts page and Submit (promoting to OPEN) or Discard (delete).

This entry point is invoked from the async tail of the block_actions dispatcher; the controller already returned 200 to Slack. We post an ephemeral receipt to the channel and replace the modal with a small "Saved" splash so the user has feedback without us racing the trigger_id TTL.

func (*SlackUseCases) HandleSlackEvent

func (uc *SlackUseCases) HandleSlackEvent(ctx context.Context, event *slackevents.EventsAPIEvent) error

HandleSlackEvent processes Slack Events API events

func (*SlackUseCases) HandleSlackMessage

func (uc *SlackUseCases) HandleSlackMessage(ctx context.Context, msg *slack.Message) error

HandleSlackMessage saves a Slack message

func (*SlackUseCases) HandleSlashCommand

func (uc *SlackUseCases) HandleSlashCommand(ctx context.Context, triggerID, userID, channelID, workspaceID, sourceTeamID, text string) error

HandleSlashCommand handles a Slack slash command.

In a channel linked to an existing case, the optional `text` subcommand chooses what to do:

  • "update" → open the case edit modal
  • "action" → open the action creation modal
  • empty → open a small choice modal that funnels into one of the above
  • anything else → ephemeral error

In a channel without a linked case, the subcommand is ignored and the existing case-creation flow runs.

func (*SlackUseCases) HandleWorkspaceSelectSubmit

func (uc *SlackUseCases) HandleWorkspaceSelectSubmit(ctx context.Context, callback *slack.InteractionCallback) (*slack.ModalViewRequest, error)

HandleWorkspaceSelectSubmit processes the workspace selection modal submission. It returns the case creation modal view to replace the current modal via response_action: update.

type SourceUseCase

type SourceUseCase struct {
	// contains filtered or unexported fields
}

SourceUseCase handles source-related business logic

func NewSourceUseCase

func NewSourceUseCase(repo interfaces.Repository, notionService notion.Service, slackService slack.Service, githubService githubAPI) *SourceUseCase

NewSourceUseCase creates a new SourceUseCase instance

func (*SourceUseCase) CreateGitHubSource

func (uc *SourceUseCase) CreateGitHubSource(ctx context.Context, workspaceID string, input CreateGitHubSourceInput) (*model.Source, error)

CreateGitHubSource creates a new GitHub source

func (*SourceUseCase) CreateNotionDBSource

func (uc *SourceUseCase) CreateNotionDBSource(ctx context.Context, workspaceID string, input CreateNotionDBSourceInput) (*model.Source, error)

CreateNotionDBSource creates a new Notion DB source with validation

func (*SourceUseCase) CreateNotionPageSource

func (uc *SourceUseCase) CreateNotionPageSource(ctx context.Context, workspaceID string, input CreateNotionPageSourceInput) (*model.Source, error)

CreateNotionPageSource creates a new Notion Page source with validation

func (*SourceUseCase) CreateSlackSource

func (uc *SourceUseCase) CreateSlackSource(ctx context.Context, workspaceID string, input CreateSlackSourceInput) (*model.Source, error)

CreateSlackSource creates a new Slack source

func (*SourceUseCase) DeleteSource

func (uc *SourceUseCase) DeleteSource(ctx context.Context, workspaceID string, id model.SourceID) error

DeleteSource removes a source

func (*SourceUseCase) GetSlackChannelNames

func (uc *SourceUseCase) GetSlackChannelNames(ctx context.Context, ids []string) (map[string]string, error)

GetSlackChannelNames retrieves channel names for given IDs

func (*SourceUseCase) GetSource

func (uc *SourceUseCase) GetSource(ctx context.Context, workspaceID string, id model.SourceID) (*model.Source, error)

GetSource retrieves a source by ID

func (*SourceUseCase) ListSlackChannels

func (uc *SourceUseCase) ListSlackChannels(ctx context.Context) ([]SlackChannelInfo, error)

ListSlackChannels lists available Slack channels

func (*SourceUseCase) ListSources

func (uc *SourceUseCase) ListSources(ctx context.Context, workspaceID string) ([]*model.Source, error)

ListSources retrieves all sources

func (*SourceUseCase) UpdateGitHubSource

func (uc *SourceUseCase) UpdateGitHubSource(ctx context.Context, workspaceID string, input UpdateGitHubSourceInput) (*model.Source, error)

UpdateGitHubSource updates a GitHub source

func (*SourceUseCase) UpdateNotionDBSource

func (uc *SourceUseCase) UpdateNotionDBSource(ctx context.Context, workspaceID string, input UpdateNotionDBSourceInput) (*model.Source, error)

UpdateNotionDBSource updates a Notion DB source.

func (*SourceUseCase) UpdateNotionPageSource

func (uc *SourceUseCase) UpdateNotionPageSource(ctx context.Context, workspaceID string, input UpdateNotionPageSourceInput) (*model.Source, error)

UpdateNotionPageSource updates a Notion Page source.

func (*SourceUseCase) UpdateSlackSource

func (uc *SourceUseCase) UpdateSlackSource(ctx context.Context, workspaceID string, input UpdateSlackSourceInput) (*model.Source, error)

UpdateSlackSource updates a Slack source

func (*SourceUseCase) UpdateSource

func (uc *SourceUseCase) UpdateSource(ctx context.Context, workspaceID string, input UpdateSourceInput) (*model.Source, error)

UpdateSource updates source common fields

func (*SourceUseCase) ValidateGitHubRepo

func (uc *SourceUseCase) ValidateGitHubRepo(ctx context.Context, repository string) (*GitHubRepoValidationResult, error)

ValidateGitHubRepo validates a GitHub repository and returns metadata

func (*SourceUseCase) ValidateNotionDB

func (uc *SourceUseCase) ValidateNotionDB(ctx context.Context, databaseID string) (*NotionDBValidationResult, error)

ValidateNotionDB validates a Notion database ID (or URL) and returns metadata

func (*SourceUseCase) ValidateNotionPage

func (uc *SourceUseCase) ValidateNotionPage(ctx context.Context, pageID string) (*NotionPageValidationResult, error)

ValidateNotionPage validates a Notion page ID (or URL) and returns metadata

type TagUseCase

type TagUseCase struct {
	// contains filtered or unexported fields
}

TagUseCase orchestrates workspace-wide Tag operations. Tags are first-class classification labels referenced by Knowledge entries via TagID.

func NewTagUseCase

func NewTagUseCase(repo interfaces.Repository) *TagUseCase

NewTagUseCase constructs a TagUseCase.

func (*TagUseCase) CreateTag

func (uc *TagUseCase) CreateTag(ctx context.Context, workspaceID, name string) (*model.Tag, error)

CreateTag creates a new tag. Name is optional and is trimmed; the ID is a freshly generated immutable TagID.

func (*TagUseCase) DeleteTag

func (uc *TagUseCase) DeleteTag(ctx context.Context, workspaceID string, id model.TagID) error

DeleteTag removes a tag, but only when no knowledge entry references it. A tag still in use is refused with ErrTagInUse so a delete can never strand a knowledge entry with a dangling tag id.

func (*TagUseCase) GetTag

func (uc *TagUseCase) GetTag(ctx context.Context, workspaceID string, id model.TagID) (*model.Tag, error)

GetTag retrieves a tag by ID.

func (*TagUseCase) ListTags

func (uc *TagUseCase) ListTags(ctx context.Context, workspaceID string) ([]*model.Tag, error)

ListTags returns every tag of a workspace, sorted by CreatedAt ascending.

func (*TagUseCase) UpdateTag

func (uc *TagUseCase) UpdateTag(ctx context.Context, workspaceID string, id model.TagID, name string) (*model.Tag, error)

UpdateTag renames an existing tag (only Name is mutable; the ID never changes).

type UpdateActionInput

type UpdateActionInput struct {
	ID             int64
	CaseID         *int64
	Title          *string
	Description    *string
	AssigneeID     *string // nil = no change; "" is not a valid clear, use ClearAssignee.
	Status         *types.ActionStatus
	DueDate        *time.Time
	ClearDueDate   bool
	ClearAssignee  bool
	SlackMessageTS *string

	SlackSync SlackSyncMode
	Actor     ActorRef

	// RejectNonHumanAssignee, when true, drops AssigneeID changes whose
	// target user is missing from the SlackUser DB. The DB only stores
	// non-bot users, so this is a guard against picks coming from the
	// Slack users_select element (which has no built-in bot filter).
	// GraphQL/WebUI callers leave this false: their pickers already show
	// only synced humans, and silently dropping the change would just
	// look like a broken UI.
	RejectNonHumanAssignee bool
}

UpdateActionInput is the unified input for ActionUseCase.UpdateAction.

type UpdateGitHubSourceInput

type UpdateGitHubSourceInput struct {
	ID           model.SourceID
	Name         *string
	Description  *string
	Repositories []string // "owner/repo" or GitHub URL format; nil means no change
	Enabled      *bool
}

UpdateGitHubSourceInput represents input for updating a GitHub source

type UpdateKnowledgeInput

type UpdateKnowledgeInput struct {
	ID     model.KnowledgeID
	Title  *string
	Claim  *string
	TagIDs *[]model.TagID
}

UpdateKnowledgeInput is the domain-level input for updating a knowledge entry. Title / Claim / TagIDs are pointers: nil means "leave unchanged".

func (UpdateKnowledgeInput) Validate

func (input UpdateKnowledgeInput) Validate() error

Validate enforces input invariants for the fields that are present.

type UpdateMemoInput

type UpdateMemoInput struct {
	ID          model.MemoID
	CaseID      int64
	Title       *string
	FieldValues map[string]model.FieldValue
}

UpdateMemoInput is the unified input for MemoUseCase.UpdateMemo. Title is a pointer so an absent title means "no change". FieldValues is a patch merged over the existing values; the merged set is then validated in full.

type UpdateNotionDBSourceInput

type UpdateNotionDBSourceInput struct {
	ID          model.SourceID
	Name        *string
	Description *string
	DatabaseID  *string
	Enabled     *bool
}

UpdateNotionDBSourceInput represents input for updating a Notion DB source. DatabaseID is a pointer so that callers can distinguish "no change" (nil) from an explicit value. When DatabaseID is non-nil, the new value is re-validated against the Notion API and the cached title/URL are refreshed.

type UpdateNotionPageSourceInput

type UpdateNotionPageSourceInput struct {
	ID          model.SourceID
	Name        *string
	Description *string
	PageID      *string
	Enabled     *bool
	Recursive   *bool
	MaxDepth    *int
}

UpdateNotionPageSourceInput represents input for updating a Notion Page source. PageID is a pointer so that callers can distinguish "no change" (nil) from an explicit value. When PageID is non-nil the new value is re-validated against the Notion API and the cached title/URL are refreshed. Recursive and MaxDepth are updated independently of PageID changes.

type UpdateSlackSourceInput

type UpdateSlackSourceInput struct {
	ID          model.SourceID
	Name        *string
	Description *string
	ChannelIDs  []string
	Enabled     *bool
}

UpdateSlackSourceInput represents input for updating a Slack source

type UpdateSourceInput

type UpdateSourceInput struct {
	ID          model.SourceID
	Name        *string
	Description *string
	Enabled     *bool
}

UpdateSourceInput represents input for updating a source

type UseCases

type UseCases struct {
	Case            *CaseUseCase
	Action          *ActionUseCase
	Memo            *MemoUseCase
	Knowledge       *KnowledgeUseCase
	Tag             *TagUseCase
	ActionStep      *ActionStepUseCase
	Agent           *AgentUseCase
	Auth            AuthUseCaseInterface
	Slack           *SlackUseCases
	Source          *SourceUseCase
	Assist          *AssistUseCase
	MentionProposal *MentionProposalUseCase
	JobRun          *JobRunUseCase
	Import          *ImportUseCase
	Dashboard       *DashboardUseCase
	// contains filtered or unexported fields
}

func New

func New(repo interfaces.Repository, registry *model.WorkspaceRegistry, opts ...Option) *UseCases

func (*UseCases) NotionToolClient

func (uc *UseCases) NotionToolClient() notiontool.Client

NotionToolClient returns the agent-tool Notion client (nil when no Notion token was configured). Exposed so the Job runtime wiring can bind notion__search / notion__get_page into the Job tool set.

func (*UseCases) SlackMessageRetriever

func (uc *UseCases) SlackMessageRetriever() slacktool.MessageRetriever

SlackMessageRetriever returns the Slack User-token-backed message retriever (nil when no User OAuth Token was configured). Exposed so the Job runtime wiring can let slack__get_messages read channels the bot has not joined.

func (*UseCases) SlackSearchService

func (uc *UseCases) SlackSearchService() slacktool.SearchService

SlackSearchService returns the Slack User-token-backed search client (nil when no User OAuth Token was configured). Exposed so the Job runtime wiring can bind slack__search_messages into the Job tool set.

func (*UseCases) SlackService

func (uc *UseCases) SlackService() slack.Service

SlackService returns the Slack service (may be nil if not configured)

func (*UseCases) ValidateDB

func (uc *UseCases) ValidateDB(ctx context.Context) (*ValidationResult, error)

ValidateDB validates that select/multi-select field values in the DB are consistent with the field schema defined in configuration. It uses count-based detection to avoid transferring document data, and only fetches a sample case when an inconsistency is found. It does NOT modify any data.

func (*UseCases) WebFetchClient

func (uc *UseCases) WebFetchClient() *webfetch.Client

WebFetchClient returns the agent webfetch client (nil when the tool is disabled or no LLM client is configured). Exposed so the Job runtime wiring can bind the same client into the Job tool set.

func (*UseCases) WorkspaceGroups

func (uc *UseCases) WorkspaceGroups() *model.WorkspaceGroupRegistry

WorkspaceGroups returns the workspace group registry. It is never nil (New initializes an empty one), so callers can List() without a nil check.

func (*UseCases) WorkspaceRegistry

func (uc *UseCases) WorkspaceRegistry() *model.WorkspaceRegistry

WorkspaceRegistry returns the workspace registry

type ValidationIssue

type ValidationIssue struct {
	WorkspaceID string
	CaseID      int64
	FieldID     string
	Message     string
	Expected    string
	Actual      string
}

ValidationIssue represents a single validation issue found during DB consistency check

type ValidationResult

type ValidationResult struct {
	Issues []ValidationIssue
}

ValidationResult holds the results of DB validation

func (*ValidationResult) AddIssue

func (r *ValidationResult) AddIssue(issue ValidationIssue)

AddIssue adds a validation issue to the result

func (*ValidationResult) HasIssues

func (r *ValidationResult) HasIssues() bool

HasIssues returns true if there are any validation issues

Directories

Path Synopsis
Package agent contains the Slack-independent agent runtime shared by the `casebound` (Case-bound mention) and `draft` (open-mode case draft) modes, and reserved for the future `triage` mode that will run after Case creation.
Package agent contains the Slack-independent agent runtime shared by the `casebound` (Case-bound mention) and `draft` (open-mode case draft) modes, and reserved for the future `triage` mode that will run after Case creation.
casebound
Package casebound contains the agent runtime for case-bound channels — Slack channels that already have an associated Case.
Package casebound contains the agent runtime for case-bound channels — Slack channels that already have an associated Case.
job
Package job is the event-driven Agent Job runtime.
Package job is the event-driven Agent Job runtime.
planexec
Package planexec hosts the reusable plan-and-execute loop shared by the proposal (case-draft) host and the planexec-strategy Job host.
Package planexec hosts the reusable plan-and-execute loop shared by the proposal (case-draft) host and the planexec-strategy Job host.
proposal
Package draft contains the open-mode (case-draft) agent runtime: a plan/execute loop that coordinates a planner LLM and parallel sub-agent investigations to produce a CaseProposal for the host (Slack) to render.
Package draft contains the open-mode (case-draft) agent runtime: a plan/execute loop that coordinates a planner LLM and parallel sub-agent investigations to produce a CaseProposal for the host (Slack) to render.
threadcase
Package threadcase hosts the thread-mode agent: a plan-and-execute turn (planexec.Runner) that runs when a Case is created from a monitored channel post (materialize the Case fields) or when the bot is mentioned in a Case thread (investigate and respond / update fields / close).
Package threadcase hosts the thread-mode agent: a plan-and-execute turn (planexec.Runner) that runs when a Case is created from a monitored channel post (materialize the Case fields) or when the bot is mentioned in a Case thread (investigate and respond / update fields / close).
wsagent
Package wsagent hosts the workspace-channel agent: a plan-and-execute turn (planexec.Runner) that runs when the bot is mentioned in a channel-mode workspace's configured workspace channel ([slack] workspace_channel).
Package wsagent hosts the workspace-channel agent: a plan-and-execute turn (planexec.Runner) that runs when the bot is mentioned in a channel-mode workspace's configured workspace channel ([slack] workspace_channel).
Package diagnosis hosts data-repair and inspection routines that operate across the persistent state owned by the rest of the application.
Package diagnosis hosts data-repair and inspection routines that operate across the persistent state owned by the rest of the application.
Package eval is the entry point of the offline eval harness: it loads scenario files, validates them (dry-run), runs each through its workflow driver, judges the produced artifact against the scenario checklist, dumps diagnostics for failing scenarios, and renders the aggregated report.
Package eval is the entry point of the offline eval harness: it loads scenario files, validates them (dry-run), runs each through its workflow driver, judges the produced artifact against the scenario checklist, dumps diagnostics for failing scenarios, and renders the aggregated report.
driver
Package driver runs a workflow type for one scenario against a prepared env and returns the artifact to judge.
Package driver runs a workflow type for one scenario against a prepared env and returns the artifact to judge.
env
Package env builds the in-memory environment a scenario runs against: a memory repository (seeded with the scenario's prior cases), in-memory history and trace repositories, the system-under-test wired via usecase.New, a recording fake Slack service, and per-tool clients that are either simulated (ToolSimulator) or live (recorded).
Package env builds the in-memory environment a scenario runs against: a memory repository (seeded with the scenario's prior cases), in-memory history and trace repositories, the system-under-test wired via usecase.New, a recording fake Slack service, and per-tool clients that are either simulated (ToolSimulator) or live (recorded).
evaltype
Package evaltype holds the shared value types and small interfaces of the eval harness.
Package evaltype holds the shared value types and small interfaces of the eval harness.
judge
Package judge evaluates a produced artifact against a scenario's checklist.
Package judge evaluates a produced artifact against a scenario's checklist.
llmrun
Package llmrun provides the gollem-backed implementation of evaltype.Completer.
Package llmrun provides the gollem-backed implementation of evaltype.Completer.
report
Package report aggregates scenario results and renders them: a human-readable stdout summary, a machine-readable JSON file, and per-scenario diagnostic dumps (see dump.go).
Package report aggregates scenario results and renders them: a human-readable stdout summary, a machine-readable JSON file, and per-scenario diagnostic dumps (see dump.go).
scenario
Package scenario defines the TOML schema for eval scenarios and loads / validates them.
Package scenario defines the TOML schema for eval scenarios and loads / validates them.
toolsim
Package toolsim provides simulated implementations of the agent's tool client interfaces.
Package toolsim provides simulated implementations of the agent's tool client interfaces.
usersim
Package usersim implements evaltype.Simulator: a simulated end-user that answers the agent's clarification questions, drawing on the scenario persona (who they are + what they know).
Package usersim implements evaltype.Simulator: a simulated end-user that answers the agent's clarification questions, drawing on the scenario persona (who they are + what they know).
Package export implements the `export` subcommand's core: reading the current state of every configured workspace out of the repository and writing it, one table per entity, to a pluggable Sink (BigQuery today).
Package export implements the `export` subcommand's core: reading the current state of every configured workspace out of the repository and writing it, one table per entity, to a pluggable Sink (BigQuery today).
Package job hosts the event-driven Agent Job lifecycle: a usecase that publishes Events, matches them to workspace Jobs, and dispatches the Job runtime in a background goroutine.
Package job hosts the event-driven Agent Job lifecycle: a usecase that publishes Events, matches them to workspace Jobs, and dispatches the Job runtime in a background goroutine.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL