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:
yushen 2026-03-24 15:59:11 +08:00
parent 3293607bef
commit 680668ffdb
11 changed files with 85 additions and 19 deletions

View file

@ -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

View file

@ -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",

View file

@ -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",

View file

@ -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),

View file

@ -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;

View file

@ -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

View file

@ -0,0 +1 @@
ALTER TABLE workspace DROP COLUMN IF EXISTS context;

View file

@ -0,0 +1 @@
ALTER TABLE workspace ADD COLUMN context TEXT;

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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