feat(workspace): add context field for AI agent background info
Add a `context` text field to workspaces, allowing users to provide background information and context for AI agents working in the workspace. Full stack: migration, sqlc queries, Go handler, TS types, SDK, and settings page UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3293607bef
commit
680668ffdb
11 changed files with 85 additions and 19 deletions
|
|
@ -95,6 +95,7 @@ export default function SettingsPage() {
|
|||
const [description, setDescription] = useState(
|
||||
workspace?.description ?? "",
|
||||
);
|
||||
const [context, setContext] = useState(workspace?.context ?? "");
|
||||
const [profileName, setProfileName] = useState(user?.name ?? "");
|
||||
const [avatarUrl, setAvatarUrl] = useState(user?.avatar_url ?? "");
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
|
@ -115,6 +116,7 @@ export default function SettingsPage() {
|
|||
useEffect(() => {
|
||||
setName(workspace?.name ?? "");
|
||||
setDescription(workspace?.description ?? "");
|
||||
setContext(workspace?.context ?? "");
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -130,6 +132,7 @@ export default function SettingsPage() {
|
|||
const updated = await api.updateWorkspace(workspace.id, {
|
||||
name,
|
||||
description: description || undefined,
|
||||
context: context || undefined,
|
||||
});
|
||||
updateWorkspace(updated);
|
||||
setSaved(true);
|
||||
|
|
@ -330,6 +333,19 @@ export default function SettingsPage() {
|
|||
placeholder="What does this workspace focus on?"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
Context
|
||||
</label>
|
||||
<textarea
|
||||
value={context}
|
||||
onChange={(e) => setContext(e.target.value)}
|
||||
rows={4}
|
||||
disabled={!canManageWorkspace}
|
||||
className="mt-1 w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring resize-none"
|
||||
placeholder="Background information and context for AI agents working in this workspace"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
Slug
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ const mockWorkspace: Workspace = {
|
|||
name: "Test WS",
|
||||
slug: "test",
|
||||
description: null,
|
||||
context: null,
|
||||
settings: {},
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
updated_at: "2026-01-01T00:00:00Z",
|
||||
|
|
@ -322,6 +323,7 @@ describe("AuthContext", () => {
|
|||
name: "Second WS",
|
||||
slug: "second",
|
||||
description: null,
|
||||
context: null,
|
||||
settings: {},
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
updated_at: "2026-01-01T00:00:00Z",
|
||||
|
|
@ -374,6 +376,7 @@ describe("AuthContext", () => {
|
|||
name: "New WS",
|
||||
slug: "new-ws",
|
||||
description: null,
|
||||
context: null,
|
||||
settings: {},
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
updated_at: "2026-01-01T00:00:00Z",
|
||||
|
|
@ -407,6 +410,7 @@ describe("AuthContext", () => {
|
|||
name: "Second WS",
|
||||
slug: "second",
|
||||
description: null,
|
||||
context: null,
|
||||
settings: {},
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
updated_at: "2026-01-01T00:00:00Z",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const mockWorkspace: Workspace = {
|
|||
name: "Test Workspace",
|
||||
slug: "test-ws",
|
||||
description: "A test workspace",
|
||||
context: null,
|
||||
settings: {},
|
||||
created_at: "2026-01-01T00:00:00Z",
|
||||
updated_at: "2026-01-01T00:00:00Z",
|
||||
|
|
|
|||
|
|
@ -237,14 +237,14 @@ export class ApiClient {
|
|||
return this.fetch(`/api/workspaces/${id}`);
|
||||
}
|
||||
|
||||
async createWorkspace(data: { name: string; slug: string; description?: string }): Promise<Workspace> {
|
||||
async createWorkspace(data: { name: string; slug: string; description?: string; context?: string }): Promise<Workspace> {
|
||||
return this.fetch("/api/workspaces", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
}
|
||||
|
||||
async updateWorkspace(id: string, data: { name?: string; description?: string; settings?: Record<string, unknown> }): Promise<Workspace> {
|
||||
async updateWorkspace(id: string, data: { name?: string; description?: string; context?: string; settings?: Record<string, unknown> }): Promise<Workspace> {
|
||||
return this.fetch(`/api/workspaces/${id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export interface Workspace {
|
|||
name: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
context: string | null;
|
||||
settings: Record<string, unknown>;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
|
|
|
|||
|
|
@ -11,13 +11,14 @@ import (
|
|||
)
|
||||
|
||||
type WorkspaceResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description *string `json:"description"`
|
||||
Settings any `json:"settings"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Context *string `json:"context"`
|
||||
Settings any `json:"settings"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
func workspaceToResponse(w db.Workspace) WorkspaceResponse {
|
||||
|
|
@ -33,6 +34,7 @@ func workspaceToResponse(w db.Workspace) WorkspaceResponse {
|
|||
Name: w.Name,
|
||||
Slug: w.Slug,
|
||||
Description: textToPtr(w.Description),
|
||||
Context: textToPtr(w.Context),
|
||||
Settings: settings,
|
||||
CreatedAt: timestampToString(w.CreatedAt),
|
||||
UpdatedAt: timestampToString(w.UpdatedAt),
|
||||
|
|
@ -95,6 +97,7 @@ type CreateWorkspaceRequest struct {
|
|||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description *string `json:"description"`
|
||||
Context *string `json:"context"`
|
||||
}
|
||||
|
||||
func (h *Handler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -128,6 +131,7 @@ func (h *Handler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
|
|||
Name: req.Name,
|
||||
Slug: req.Slug,
|
||||
Description: ptrToText(req.Description),
|
||||
Context: ptrToText(req.Context),
|
||||
})
|
||||
if err != nil {
|
||||
if isUniqueViolation(err) {
|
||||
|
|
@ -159,6 +163,7 @@ func (h *Handler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
|
|||
type UpdateWorkspaceRequest struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
Context *string `json:"context"`
|
||||
Settings any `json:"settings"`
|
||||
}
|
||||
|
||||
|
|
@ -188,6 +193,9 @@ func (h *Handler) UpdateWorkspace(w http.ResponseWriter, r *http.Request) {
|
|||
if req.Description != nil {
|
||||
params.Description = pgtype.Text{String: *req.Description, Valid: true}
|
||||
}
|
||||
if req.Context != nil {
|
||||
params.Context = pgtype.Text{String: *req.Context, Valid: true}
|
||||
}
|
||||
if req.Settings != nil {
|
||||
s, _ := json.Marshal(req.Settings)
|
||||
params.Settings = s
|
||||
|
|
|
|||
1
server/migrations/006_workspace_context.down.sql
Normal file
1
server/migrations/006_workspace_context.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE workspace DROP COLUMN IF EXISTS context;
|
||||
1
server/migrations/006_workspace_context.up.sql
Normal file
1
server/migrations/006_workspace_context.up.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE workspace ADD COLUMN context TEXT;
|
||||
|
|
@ -92,6 +92,24 @@ type DaemonConnection struct {
|
|||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type DaemonPairingSession struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Token string `json:"token"`
|
||||
DaemonID string `json:"daemon_id"`
|
||||
DeviceName string `json:"device_name"`
|
||||
RuntimeName string `json:"runtime_name"`
|
||||
RuntimeType string `json:"runtime_type"`
|
||||
RuntimeVersion string `json:"runtime_version"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
ApprovedBy pgtype.UUID `json:"approved_by"`
|
||||
Status string `json:"status"`
|
||||
ApprovedAt pgtype.Timestamptz `json:"approved_at"`
|
||||
ClaimedAt pgtype.Timestamptz `json:"claimed_at"`
|
||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type InboxItem struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
|
|
@ -172,4 +190,5 @@ type Workspace struct {
|
|||
Settings []byte `json:"settings"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Context pgtype.Text `json:"context"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,19 +12,25 @@ import (
|
|||
)
|
||||
|
||||
const createWorkspace = `-- name: CreateWorkspace :one
|
||||
INSERT INTO workspace (name, slug, description)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at
|
||||
INSERT INTO workspace (name, slug, description, context)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at, context
|
||||
`
|
||||
|
||||
type CreateWorkspaceParams struct {
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Context pgtype.Text `json:"context"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateWorkspace(ctx context.Context, arg CreateWorkspaceParams) (Workspace, error) {
|
||||
row := q.db.QueryRow(ctx, createWorkspace, arg.Name, arg.Slug, arg.Description)
|
||||
row := q.db.QueryRow(ctx, createWorkspace,
|
||||
arg.Name,
|
||||
arg.Slug,
|
||||
arg.Description,
|
||||
arg.Context,
|
||||
)
|
||||
var i Workspace
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
|
|
@ -34,6 +40,7 @@ func (q *Queries) CreateWorkspace(ctx context.Context, arg CreateWorkspaceParams
|
|||
&i.Settings,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -48,7 +55,7 @@ func (q *Queries) DeleteWorkspace(ctx context.Context, id pgtype.UUID) error {
|
|||
}
|
||||
|
||||
const getWorkspace = `-- name: GetWorkspace :one
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at FROM workspace
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at, context FROM workspace
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
|
|
@ -63,12 +70,13 @@ func (q *Queries) GetWorkspace(ctx context.Context, id pgtype.UUID) (Workspace,
|
|||
&i.Settings,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceBySlug = `-- name: GetWorkspaceBySlug :one
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at FROM workspace
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at, context FROM workspace
|
||||
WHERE slug = $1
|
||||
`
|
||||
|
||||
|
|
@ -83,12 +91,13 @@ func (q *Queries) GetWorkspaceBySlug(ctx context.Context, slug string) (Workspac
|
|||
&i.Settings,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listWorkspaces = `-- name: ListWorkspaces :many
|
||||
SELECT w.id, w.name, w.slug, w.description, w.settings, w.created_at, w.updated_at FROM workspace w
|
||||
SELECT w.id, w.name, w.slug, w.description, w.settings, w.created_at, w.updated_at, w.context FROM workspace w
|
||||
JOIN member m ON m.workspace_id = w.id
|
||||
WHERE m.user_id = $1
|
||||
ORDER BY w.created_at ASC
|
||||
|
|
@ -111,6 +120,7 @@ func (q *Queries) ListWorkspaces(ctx context.Context, userID pgtype.UUID) ([]Wor
|
|||
&i.Settings,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -126,16 +136,18 @@ const updateWorkspace = `-- name: UpdateWorkspace :one
|
|||
UPDATE workspace SET
|
||||
name = COALESCE($2, name),
|
||||
description = COALESCE($3, description),
|
||||
settings = COALESCE($4, settings),
|
||||
context = COALESCE($4, context),
|
||||
settings = COALESCE($5, settings),
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at, context
|
||||
`
|
||||
|
||||
type UpdateWorkspaceParams struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Name pgtype.Text `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Context pgtype.Text `json:"context"`
|
||||
Settings []byte `json:"settings"`
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +156,7 @@ func (q *Queries) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams
|
|||
arg.ID,
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.Context,
|
||||
arg.Settings,
|
||||
)
|
||||
var i Workspace
|
||||
|
|
@ -155,6 +168,7 @@ func (q *Queries) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams
|
|||
&i.Settings,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,14 +13,15 @@ SELECT * FROM workspace
|
|||
WHERE slug = $1;
|
||||
|
||||
-- name: CreateWorkspace :one
|
||||
INSERT INTO workspace (name, slug, description)
|
||||
VALUES ($1, $2, $3)
|
||||
INSERT INTO workspace (name, slug, description, context)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateWorkspace :one
|
||||
UPDATE workspace SET
|
||||
name = COALESCE(sqlc.narg('name'), name),
|
||||
description = COALESCE(sqlc.narg('description'), description),
|
||||
context = COALESCE(sqlc.narg('context'), context),
|
||||
settings = COALESCE(sqlc.narg('settings'), settings),
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue