Let a queued message be taken back into the composer
This commit is contained in:
@@ -75,7 +75,42 @@ func (m *Manager) Message(ctx context.Context, sessionID, actor, text, model str
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Withdraw(ctx context.Context, taskID string, inputID int64, actor string) error {
|
||||
task, err := m.store.GetTask(ctx, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sessions, err := m.store.ListSessions(ctx, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(sessions) == 0 {
|
||||
return fmt.Errorf("%w: message %d is not queued", ErrConflict, inputID)
|
||||
}
|
||||
sess := sessions[len(sessions)-1]
|
||||
|
||||
unlock := m.lockDelivery(queueLockKey(taskID))
|
||||
defer unlock()
|
||||
|
||||
err = m.store.WithdrawTaskInput(ctx, taskID, inputID, task.ProjectId, sess.Id, actor)
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
return fmt.Errorf("%w: message %d is not queued", ErrConflict, inputID)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
slog.Info("queued message withdrawn", "session", sess.Id, "task", taskID, "input", inputID, "actor", actor)
|
||||
return nil
|
||||
}
|
||||
|
||||
func queueLockKey(taskID string) string {
|
||||
return "task:" + taskID
|
||||
}
|
||||
|
||||
func (m *Manager) deliverQueued(ctx context.Context, sess api.AgentSession) (bool, error) {
|
||||
unlock := m.lockDelivery(queueLockKey(sess.TaskId))
|
||||
defer unlock()
|
||||
|
||||
inputs, err := m.store.TaskInputs(ctx, sess.TaskId)
|
||||
if err != nil || len(inputs) == 0 {
|
||||
return false, err
|
||||
@@ -131,6 +166,9 @@ func (m *Manager) takeQueuedMidTurn(ctx context.Context, l *live) []store.TaskIn
|
||||
return nil
|
||||
}
|
||||
|
||||
unlock := m.lockDelivery(queueLockKey(l.taskID))
|
||||
defer unlock()
|
||||
|
||||
inputs, err := m.store.TaskInputs(ctx, l.taskID)
|
||||
if err != nil {
|
||||
slog.Error("list queued messages", "session", l.id, "err", err)
|
||||
|
||||
+60
-19
@@ -1624,6 +1624,9 @@ type ServerInterface interface {
|
||||
// (GET /api/tasks/{taskId}/previews)
|
||||
ListPreviews(w http.ResponseWriter, r *http.Request, taskId string)
|
||||
|
||||
// (DELETE /api/tasks/{taskId}/queue/{inputId})
|
||||
WithdrawMessage(w http.ResponseWriter, r *http.Request, taskId string, inputId int64)
|
||||
|
||||
// (GET /api/tasks/{taskId}/reviews)
|
||||
ListReviews(w http.ResponseWriter, r *http.Request, taskId string)
|
||||
|
||||
@@ -3008,6 +3011,41 @@ func (siw *ServerInterfaceWrapper) ListPreviews(w http.ResponseWriter, r *http.R
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// WithdrawMessage operation middleware
|
||||
func (siw *ServerInterfaceWrapper) WithdrawMessage(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var err error
|
||||
_ = err
|
||||
|
||||
// ------------- Path parameter "taskId" -------------
|
||||
var taskId string
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "taskId", r.PathValue("taskId"), &taskId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "string", Format: "", ValueIsUnescaped: true})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "taskId", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Path parameter "inputId" -------------
|
||||
var inputId int64
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "inputId", r.PathValue("inputId"), &inputId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true, Type: "integer", Format: "int64", ValueIsUnescaped: true})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "inputId", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.WithdrawMessage(w, r, taskId, inputId)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// ListReviews operation middleware
|
||||
func (siw *ServerInterfaceWrapper) ListReviews(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -3562,6 +3600,7 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/tasks/{taskId}/snapshots/{n}/fork", wrapper.ForkSnapshot)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/tasks/{taskId}/sessions", wrapper.ListSessions)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/tasks/{taskId}/sessions", wrapper.CreateSession)
|
||||
m.HandleFunc(http.MethodDelete+" "+options.BaseURL+"/api/tasks/{taskId}/queue/{inputId}", wrapper.WithdrawMessage)
|
||||
m.HandleFunc(http.MethodGet+" "+options.BaseURL+"/api/sessions/{sessionId}", wrapper.GetSession)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/messages", wrapper.SendMessage)
|
||||
m.HandleFunc(http.MethodPost+" "+options.BaseURL+"/api/sessions/{sessionId}/compact", wrapper.CompactSession)
|
||||
@@ -3798,25 +3837,27 @@ var swaggerSpec = []string{
|
||||
"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",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
|
||||
@@ -779,6 +779,15 @@ func (s *Server) SendMessage(w http.ResponseWriter, r *http.Request, sessionID s
|
||||
writeJSON(w, http.StatusAccepted, sess)
|
||||
}
|
||||
|
||||
func (s *Server) WithdrawMessage(w http.ResponseWriter, r *http.Request, taskID string, inputID int64) {
|
||||
err := s.agents.Withdraw(r.Context(), taskID, inputID, actor(r))
|
||||
if err != nil {
|
||||
writeAgentError(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (s *Server) CompactSession(w http.ResponseWriter, r *http.Request, sessionID string) {
|
||||
sess, err := s.agents.Compact(r.Context(), sessionID)
|
||||
if err != nil {
|
||||
|
||||
@@ -26,15 +26,36 @@ func (s *Store) AddTaskInput(ctx context.Context, in TaskInput, projectID, sessi
|
||||
payload["images"] = in.Images
|
||||
}
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO task_inputs (task_id, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?)`,
|
||||
res, err := tx.ExecContext(ctx, `INSERT INTO task_inputs (task_id, actor, text, images, created_at) VALUES (?, ?, ?, ?, ?)`,
|
||||
in.TaskID, in.Actor, in.Text, string(images), now())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload["id"], err = res.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(sessionEvent("user.queued", projectID, in.TaskID, "", sessionID, in.Actor, payload))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) WithdrawTaskInput(ctx context.Context, taskID string, id int64, projectID, sessionID, actor string) error {
|
||||
return s.write(ctx, func(tx *sql.Tx, emit emitFunc) error {
|
||||
res, err := tx.ExecContext(ctx, `DELETE FROM task_inputs WHERE id = ? AND task_id = ?`, id, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return emit(sessionEvent("user.unqueued", projectID, taskID, "", sessionID, actor, map[string]any{"id": id}))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) TaskInputs(ctx context.Context, taskID string) ([]TaskInput, error) {
|
||||
rows, err := s.db.QueryContext(ctx, `SELECT id, task_id, actor, text, images, created_at FROM task_inputs WHERE task_id = ? ORDER BY id`, taskID)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user