Documentation
¶
Overview ¶
Package watcher defines watchers: scripts ask runs on a schedule. A run whose script prints nothing does nothing; a run whose script prints anything hands that output, with the watcher's instructions, to one model run — a chat session, a named agent, or a workflow.
This package owns the definitions and their store, the schedule grammar, the state every ask window shares, the lock that picks the one window that runs watchers, and the script runner. Running the model is the TUI's job (cmd/ask).
Index ¶
- Constants
- Variables
- func Clip(s string, max int) string
- func ClipTail(s string, max int) string
- func DefPath(r Ref) string
- func Delete(r Ref) error
- func Dir() string
- func MutateState(fn func(*State) error) error
- func NextDue(d Def, st *Status) time.Time
- func ProjectDir(project string) string
- func ProjectOf(dir string) string
- func Prompt(d Def, res ScriptResult, at time.Time) string
- func ReadScript(r Ref) (string, error)
- func ScriptPath(r Ref) string
- func StateDir(r Ref) string
- func StatePath() string
- func Summary(d Def, st *Status) string
- func ValidName(name string) error
- type Def
- type Lead
- type Leader
- type Phase
- type Ref
- type Run
- type RunStatus
- type Schedule
- type ScriptResult
- type ScriptSpec
- type State
- type StateStamp
- type Status
- type TargetKind
Constants ¶
const ( // DefaultTimeout bounds a script that sets no timeout of its own. DefaultTimeout = 2 * time.Minute // MaxTimeout is the longest a script may run before it is killed. MaxTimeout = 30 * time.Minute )
const ( // MinInterval is the shortest interval a schedule may name. MinInterval = 10 * time.Second // MaxInterval is the longest. MaxInterval = 30 * 24 * time.Hour )
const ( EnvName = "ASK_WATCHER_NAME" EnvStateDir = "ASK_WATCHER_STATE_DIR" EnvLastRun = "ASK_WATCHER_LAST_RUN" )
Environment a watcher script runs with, on top of ask's own.
const ( // MaxRuns is how many runs each watcher keeps in the shared state. MaxRuns = 20 // MaxStoredOutput caps the script output a run keeps for display; the // model gets the whole output, up to MaxOutputBytes. MaxStoredOutput = 16 * 1024 // MaxStoredResponse caps the model's final answer a run keeps. MaxStoredResponse = 8 * 1024 // MaxStoredStderr caps the tail of stderr a run keeps. MaxStoredStderr = 4 * 1024 )
const MaxOutputBytes = 100 * 1024
MaxOutputBytes caps the script output handed to the model; longer output keeps its head and tail.
Variables ¶
var ErrNotFound = errors.New("watcher not found")
ErrNotFound is returned for a watcher with no definition.
var RunScript = runScript
RunScript runs one check. Swappable so tests never start a process.
Functions ¶
func Delete ¶
Delete removes a watcher: its definition, script, scratch directory, and shared run state.
func Dir ¶
func Dir() string
Dir is ~/.config/ask/watchers: a directory per project holding each watcher's <name>.json, its <name>.script when it has one, and its <name>.state/ scratch directory.
func MutateState ¶
MutateState runs fn on the shared state under a cross-process lock and writes the result.
func NextDue ¶
NextDue is when the watcher's next check is due: what the running window last computed, else what the schedule says. Zero for a paused watcher or an unparseable schedule.
func ProjectDir ¶
ProjectDir is the store directory a project's watchers live in: the project's base name, sanitized, and a hash of its path, so two projects with one name never share it.
func ProjectOf ¶
ProjectOf is the project a directory belongs to: the root of its git checkout — an ask worktree maps back to the checkout it was made from — else the directory itself.
func Prompt ¶
func Prompt(d Def, res ScriptResult, at time.Time) string
Prompt is the first message of a watcher's model run: its instructions, then what its script printed. A workflow target gets the same text as the run's source.
func ReadScript ¶
ReadScript returns a watcher's stored script, or "" when it runs a command.
func ScriptPath ¶
ScriptPath is where a watcher's stored script lives.
func StateDir ¶
StateDir is the directory a watcher's script may keep its own state in (what it reported last time, a cursor, a cache). The runner creates it and passes it as ASK_WATCHER_STATE_DIR; deleting the watcher removes it.
Types ¶
type Def ¶
type Def struct {
Project string `json:"project"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Schedule string `json:"schedule"`
Command string `json:"command,omitempty"`
Cwd string `json:"cwd"`
TimeoutSecs int `json:"timeoutSeconds,omitempty"`
Instructions string `json:"instructions,omitempty"`
Agent string `json:"agent,omitempty"`
Workflow string `json:"workflow,omitempty"`
Provider string `json:"provider,omitempty"`
Model string `json:"model,omitempty"`
Paused bool `json:"paused,omitempty"`
CreatedAt time.Time `json:"createdAt"`
// HasScript reports a script file stored with the definition. It is
// derived from the store, never serialized.
HasScript bool `json:"-"`
}
Def is one watcher. The script is either Command, run by the shell, or a script file stored beside the definition (HasScript), run directly.
A watcher belongs to a project: the project its working directory is in (ProjectOf), set by Save. Names are unique within a project; a watcher is named by its Ref (project and name), or as one string by its ID.
func List ¶
func List() []Def
List returns every stored watcher in every project, by project then name. Malformed files, and files that disagree with where they are stored, are skipped.
func Resolve ¶
Resolve finds a watcher by name: in project when it has one by that name, else the one watcher of that name anywhere. More than one elsewhere is ambiguous.
func Save ¶
Save stores d in the project its working directory belongs to (which Save sets as d.Project). old is the watcher being edited, the zero Ref to create one. script replaces the stored script when non-nil (an empty string removes it); nil keeps whatever is stored. A rename, or a move to another project, carries the script, the scratch directory, and the shared run state along. HasScript is derived from the result, and the definition is validated against it.
func (Def) RunsLabel ¶
RunsLabel is what the watcher runs, for display: the command, or the stored script.
func (Def) Target ¶
func (d Def) Target() TargetKind
Target reports what the watcher hands its output to.
func (Def) TargetLabel ¶
TargetLabel names the target for display: "chat session", "agent x", "workflow y".
type Lead ¶
type Lead struct {
// contains filtered or unexported fields
}
Lead is held by the one ask process that runs watchers. It is an advisory lock on ~/.config/ask/watchers.lock kept for as long as the process leads; the kernel drops it when the process dies, however it dies, so there is never a stale lead to clean up.
type Run ¶
type Run struct {
ID string `json:"id"`
StartedAt time.Time `json:"startedAt"`
EndedAt time.Time `json:"endedAt,omitempty"`
Status RunStatus `json:"status"`
Error string `json:"error,omitempty"`
ExitCode int `json:"exitCode"`
Output string `json:"output,omitempty"`
OutputBytes int `json:"outputBytes,omitempty"`
Stderr string `json:"stderr,omitempty"`
Target string `json:"target,omitempty"`
Provider string `json:"provider,omitempty"`
Model string `json:"model,omitempty"`
// SessionID and SessionCwd locate the model run's session on disk, so
// any window can show its transcript and continue it as a chat.
SessionID string `json:"sessionID,omitempty"`
SessionCwd string `json:"sessionCwd,omitempty"`
Response string `json:"response,omitempty"`
CostUSD float64 `json:"costUSD,omitempty"`
CostKnown bool `json:"costKnown,omitempty"`
}
Run is one check that did something: its script failed, or it printed output that went to the model. Checks that printed nothing are not runs; they only move Status.LastCheck.
type Schedule ¶
type Schedule struct {
// contains filtered or unexported fields
}
Schedule is a parsed watcher schedule, in one of three shapes:
every 5m an interval, counted from the end of the last run
every 10m weekdays 09:00-18:00 an interval that only runs inside those days and hours
daily 09:00 times of day ("weekdays", "weekends", "mon,thu", "mon-fri" too)
hourly :15,:45 minutes past every hour
Times are local. Whatever the shape, a run is never due until the previous one has ended: an interval counts from that end, and a time of day is the first one after it.
func ParseSchedule ¶
ParseSchedule parses a schedule string. See Schedule for the grammar.
func (Schedule) IsInterval ¶
IsInterval reports whether the schedule counts an interval from the end of the last run.
func (Schedule) Next ¶
Next is when the next run is due: for an interval, its length after lastEnd (or anchor, the moment the watcher was created, when it never ran — so a new interval watcher runs straight away), moved into the schedule's days and hours; for times of day, the first one after lastEnd (or anchor). A due time in the past means "run now": missed runs are never made up, so at most one run follows.
type ScriptResult ¶
type ScriptResult struct {
ExitCode int
// Stdout is what the script printed, cut to MaxOutputBytes;
// StdoutBytes is how much it printed in all.
Stdout string
StdoutBytes int
// Stderr is the tail of what the script wrote to stderr.
Stderr string
TimedOut bool
Cancelled bool
// Err is set when the script could not be started.
Err error
}
ScriptResult is how a script ended and what it printed.
func (ScriptResult) Failed ¶
func (r ScriptResult) Failed() bool
Failed reports whether the check failed: it did not start, timed out, was stopped, or exited non-zero.
func (ScriptResult) FailureText ¶
func (r ScriptResult) FailureText() string
FailureText describes a failed check in a few words.
type ScriptSpec ¶
type ScriptSpec struct {
Dir string
// Command runs through Shell -c; when empty, Path is executed directly.
Command string
Path string
Shell string
Env []string
Timeout time.Duration
}
ScriptSpec is one script invocation.
func SpecFor ¶
func SpecFor(d Def, shell string, lastCheck time.Time) ScriptSpec
SpecFor builds the invocation for d: its command or stored script, in its working directory, with its timeout, and the watcher environment — its name, its scratch directory (created here), and when it last ran (RFC 3339, empty the first time).
type State ¶
type State struct {
Version int `json:"version"`
Leader Leader `json:"leader"`
Watchers map[string]*Status `json:"watchers,omitempty"`
}
State is shared by every ask window: the one that runs watchers writes it, the rest read it to show the same status, and any window may file a request (run now, stop, seen).
type StateStamp ¶
StateStamp identifies a version of the state file on disk, so a reader re-reads it only when it changed.
func CurrentStateStamp ¶
func CurrentStateStamp() (StateStamp, bool)
CurrentStateStamp stats the state file; ok is false when it is missing.
type Status ¶
type Status struct {
Phase Phase `json:"phase,omitempty"`
Activity string `json:"activity,omitempty"`
// Approval names the tool a running model is waiting on the user to
// approve.
Approval string `json:"approval,omitempty"`
// LastCheck is when the script last started; LastEnded when the last
// check (and its model run) ended. The schedule counts from LastEnded.
LastCheck time.Time `json:"lastCheck,omitempty"`
LastEnded time.Time `json:"lastEnded,omitempty"`
// LastNote describes the last check when it produced no run ("no
// output"); empty when it did.
LastNote string `json:"lastNote,omitempty"`
NextRun time.Time `json:"nextRun,omitempty"`
Runs []Run `json:"runs,omitempty"`
// RunRequested and StopRequested are requests from any window to the
// one that runs watchers: run now, and stop the run with that id.
RunRequested time.Time `json:"runRequested,omitempty"`
StopRequested string `json:"stopRequested,omitempty"`
// SeenRun is the newest run the user has looked at.
SeenRun string `json:"seenRun,omitempty"`
}
Status is one watcher's live state and recent runs.
type TargetKind ¶
type TargetKind int
TargetKind is what a watcher's output is handed to.
const ( // TargetChat is a normal ask session running the watcher's instructions. TargetChat TargetKind = iota // TargetAgent is a named agent definition. TargetAgent // TargetWorkflow is a workflow run on the output. TargetWorkflow )