Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
207 lines
6.8 KiB
Go
207 lines
6.8 KiB
Go
package workspace
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
workspacebiz "github.com/tutti-os/tutti/services/tuttid/biz/workspace"
|
|
)
|
|
|
|
func (s *SQLiteStore) PutAppFactoryJob(ctx context.Context, job workspacebiz.AppFactoryJob) error {
|
|
if s == nil || s.writeDB == nil {
|
|
return errors.New("workspace database is not initialized")
|
|
}
|
|
|
|
workspaceID := strings.TrimSpace(job.WorkspaceID)
|
|
jobID := strings.TrimSpace(job.JobID)
|
|
status := strings.TrimSpace(string(job.Status))
|
|
if workspaceID == "" || jobID == "" || status == "" {
|
|
return errors.New("workspace id, app factory job id, and status are required")
|
|
}
|
|
|
|
now := unixMs(time.Now().UTC())
|
|
createdAt := job.CreatedAtUnixMs
|
|
if createdAt == 0 {
|
|
createdAt = now
|
|
}
|
|
updatedAt := job.UpdatedAtUnixMs
|
|
if updatedAt == 0 {
|
|
updatedAt = now
|
|
}
|
|
|
|
_, err := s.writeDB.ExecContext(ctx, `
|
|
INSERT INTO app_factory_jobs (
|
|
workspace_id, job_id, status, prompt, app_id, display_name, description,
|
|
agent_target_id, provider, model, reasoning_effort, agent_session_id, draft_dir, runtime_dir, data_dir, log_dir,
|
|
package_dir, validation_result_json, failure_reason, published_version,
|
|
created_at_unix_ms, updated_at_unix_ms
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(workspace_id, job_id) DO UPDATE SET
|
|
status = excluded.status,
|
|
prompt = excluded.prompt,
|
|
app_id = excluded.app_id,
|
|
display_name = excluded.display_name,
|
|
description = excluded.description,
|
|
agent_target_id = excluded.agent_target_id,
|
|
provider = excluded.provider,
|
|
model = excluded.model,
|
|
reasoning_effort = excluded.reasoning_effort,
|
|
agent_session_id = excluded.agent_session_id,
|
|
draft_dir = excluded.draft_dir,
|
|
runtime_dir = excluded.runtime_dir,
|
|
data_dir = excluded.data_dir,
|
|
log_dir = excluded.log_dir,
|
|
package_dir = excluded.package_dir,
|
|
validation_result_json = excluded.validation_result_json,
|
|
failure_reason = excluded.failure_reason,
|
|
published_version = excluded.published_version,
|
|
updated_at_unix_ms = excluded.updated_at_unix_ms
|
|
`, workspaceID, jobID, status, strings.TrimSpace(job.Prompt), strings.TrimSpace(job.AppID),
|
|
strings.TrimSpace(job.DisplayName), strings.TrimSpace(job.Description),
|
|
strings.TrimSpace(job.AgentTargetID),
|
|
strings.TrimSpace(job.Provider), strings.TrimSpace(job.Model), strings.TrimSpace(job.ReasoningEffort),
|
|
strings.TrimSpace(job.AgentSessionID), strings.TrimSpace(job.DraftDir),
|
|
strings.TrimSpace(job.RuntimeDir), strings.TrimSpace(job.DataDir),
|
|
strings.TrimSpace(job.LogDir), strings.TrimSpace(job.PackageDir),
|
|
strings.TrimSpace(job.ValidationResultJSON), strings.TrimSpace(job.FailureReason),
|
|
strings.TrimSpace(job.PublishedVersion), createdAt, updatedAt)
|
|
if err != nil {
|
|
return fmt.Errorf("put workspace app factory job: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *SQLiteStore) GetAppFactoryJob(ctx context.Context, workspaceID string, jobID string) (workspacebiz.AppFactoryJob, error) {
|
|
if s == nil || s.writeDB == nil {
|
|
return workspacebiz.AppFactoryJob{}, errors.New("workspace database is not initialized")
|
|
}
|
|
workspaceID = strings.TrimSpace(workspaceID)
|
|
jobID = strings.TrimSpace(jobID)
|
|
if workspaceID == "" || jobID == "" {
|
|
return workspacebiz.AppFactoryJob{}, errors.New("workspace id and app factory job id are required")
|
|
}
|
|
|
|
row := s.readDB.QueryRowContext(ctx, `
|
|
SELECT workspace_id, job_id, status, prompt, app_id, display_name, description,
|
|
agent_target_id, provider, model, reasoning_effort, agent_session_id, draft_dir, runtime_dir, data_dir, log_dir,
|
|
package_dir, validation_result_json, failure_reason, published_version,
|
|
created_at_unix_ms, updated_at_unix_ms
|
|
FROM app_factory_jobs
|
|
WHERE workspace_id = ? AND job_id = ?
|
|
`, workspaceID, jobID)
|
|
job, err := scanAppFactoryJob(row)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return workspacebiz.AppFactoryJob{}, ErrWorkspaceAppFactoryJobNotFound
|
|
}
|
|
return workspacebiz.AppFactoryJob{}, fmt.Errorf("get workspace app factory job: %w", err)
|
|
}
|
|
return job, nil
|
|
}
|
|
|
|
func (s *SQLiteStore) ListAppFactoryJobs(ctx context.Context, workspaceID string) ([]workspacebiz.AppFactoryJob, error) {
|
|
if s == nil || s.writeDB == nil {
|
|
return nil, errors.New("workspace database is not initialized")
|
|
}
|
|
workspaceID = strings.TrimSpace(workspaceID)
|
|
if workspaceID == "" {
|
|
return nil, errors.New("workspace id is required")
|
|
}
|
|
|
|
rows, err := s.readDB.QueryContext(ctx, `
|
|
SELECT workspace_id, job_id, status, prompt, app_id, display_name, description,
|
|
agent_target_id, provider, model, reasoning_effort, agent_session_id, draft_dir, runtime_dir, data_dir, log_dir,
|
|
package_dir, validation_result_json, failure_reason, published_version,
|
|
created_at_unix_ms, updated_at_unix_ms
|
|
FROM app_factory_jobs
|
|
WHERE workspace_id = ?
|
|
ORDER BY updated_at_unix_ms DESC, job_id ASC
|
|
`, workspaceID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list workspace app factory jobs: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var jobs []workspacebiz.AppFactoryJob
|
|
for rows.Next() {
|
|
job, err := scanAppFactoryJob(rows)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scan workspace app factory job: %w", err)
|
|
}
|
|
jobs = append(jobs, job)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate workspace app factory jobs: %w", err)
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (s *SQLiteStore) DeleteAppFactoryJob(ctx context.Context, workspaceID string, jobID string) error {
|
|
if s == nil || s.writeDB == nil {
|
|
return errors.New("workspace database is not initialized")
|
|
}
|
|
workspaceID = strings.TrimSpace(workspaceID)
|
|
jobID = strings.TrimSpace(jobID)
|
|
if workspaceID == "" || jobID == "" {
|
|
return errors.New("workspace id and app factory job id are required")
|
|
}
|
|
|
|
result, err := s.writeDB.ExecContext(ctx, `
|
|
DELETE FROM app_factory_jobs
|
|
WHERE workspace_id = ? AND job_id = ?
|
|
`, workspaceID, jobID)
|
|
if err != nil {
|
|
return fmt.Errorf("delete workspace app factory job: %w", err)
|
|
}
|
|
rowsAffected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("delete workspace app factory job rows affected: %w", err)
|
|
}
|
|
if rowsAffected == 0 {
|
|
return ErrWorkspaceAppFactoryJobNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type appFactoryJobScanner interface {
|
|
Scan(dest ...any) error
|
|
}
|
|
|
|
func scanAppFactoryJob(scanner appFactoryJobScanner) (workspacebiz.AppFactoryJob, error) {
|
|
var job workspacebiz.AppFactoryJob
|
|
var status string
|
|
if err := scanner.Scan(
|
|
&job.WorkspaceID,
|
|
&job.JobID,
|
|
&status,
|
|
&job.Prompt,
|
|
&job.AppID,
|
|
&job.DisplayName,
|
|
&job.Description,
|
|
&job.AgentTargetID,
|
|
&job.Provider,
|
|
&job.Model,
|
|
&job.ReasoningEffort,
|
|
&job.AgentSessionID,
|
|
&job.DraftDir,
|
|
&job.RuntimeDir,
|
|
&job.DataDir,
|
|
&job.LogDir,
|
|
&job.PackageDir,
|
|
&job.ValidationResultJSON,
|
|
&job.FailureReason,
|
|
&job.PublishedVersion,
|
|
&job.CreatedAtUnixMs,
|
|
&job.UpdatedAtUnixMs,
|
|
); err != nil {
|
|
return workspacebiz.AppFactoryJob{}, err
|
|
}
|
|
job.Status = workspacebiz.AppFactoryJobStatus(strings.TrimSpace(status))
|
|
return job, nil
|
|
}
|