Changeset 0.21.5 (#91)
This commit is contained in:
@@ -60,6 +60,9 @@ var routeTable = map[string]Direction{
|
||||
"notification.new": DirToClient, // targeted via Hub.SendToUser, not room-based
|
||||
"notification.read": DirToClient, // badge sync across tabs
|
||||
|
||||
// Workspace (v0.21.5)
|
||||
"workspace.file.": DirToClient, // file changed events for live editor updates
|
||||
|
||||
// Plugin hooks — never cross the wire
|
||||
"plugin.hook.": DirLocal,
|
||||
"internal.": DirLocal,
|
||||
|
||||
@@ -38,7 +38,8 @@ type updateChannelRequest struct {
|
||||
IsPinned *bool `json:"is_pinned,omitempty"`
|
||||
Folder *string `json:"folder,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Settings *json.RawMessage `json:"settings,omitempty"` // JSONB merge into existing settings
|
||||
Settings *json.RawMessage `json:"settings,omitempty"` // JSONB merge into existing settings
|
||||
WorkspaceID *string `json:"workspace_id,omitempty"` // bind workspace (v0.21.5)
|
||||
}
|
||||
|
||||
type channelResponse struct {
|
||||
@@ -54,6 +55,7 @@ type channelResponse struct {
|
||||
IsPinned bool `json:"is_pinned"`
|
||||
Folder *string `json:"folder"`
|
||||
ProjectID *string `json:"project_id,omitempty"`
|
||||
WorkspaceID *string `json:"workspace_id,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
Settings json.RawMessage `json:"settings,omitempty"`
|
||||
MessageCount int `json:"message_count"`
|
||||
@@ -178,7 +180,7 @@ func (h *ChannelHandler) ListChannels(c *gin.Context) {
|
||||
// Fetch channels with message count
|
||||
query := `
|
||||
SELECT c.id, c.user_id, c.title, c.type, c.description, c.model, c.provider_config_id,
|
||||
c.system_prompt, c.is_archived, c.is_pinned, c.folder, c.project_id,
|
||||
c.system_prompt, c.is_archived, c.is_pinned, c.folder, c.project_id, c.workspace_id,
|
||||
c.tags, c.settings,
|
||||
COALESCE(mc.cnt, 0) AS message_count,
|
||||
c.created_at, c.updated_at
|
||||
@@ -230,7 +232,7 @@ func (h *ChannelHandler) ListChannels(c *gin.Context) {
|
||||
var tags []string
|
||||
err := rows.Scan(
|
||||
&ch.ID, &ch.UserID, &ch.Title, &ch.Type, &ch.Description, &ch.Model, &ch.APIConfigID,
|
||||
&ch.SystemPrompt, &ch.IsArchived, &ch.IsPinned, &ch.Folder, &ch.ProjectID,
|
||||
&ch.SystemPrompt, &ch.IsArchived, &ch.IsPinned, &ch.Folder, &ch.ProjectID, &ch.WorkspaceID,
|
||||
scanTags(&tags), scanJSON(&ch.Settings),
|
||||
&ch.MessageCount, &ch.CreatedAt, &ch.UpdatedAt,
|
||||
)
|
||||
@@ -377,7 +379,7 @@ func (h *ChannelHandler) GetChannel(c *gin.Context) {
|
||||
var tags []string
|
||||
err := database.DB.QueryRow(database.Q(`
|
||||
SELECT c.id, c.user_id, c.title, c.type, c.description, c.model, c.provider_config_id,
|
||||
c.system_prompt, c.is_archived, c.is_pinned, c.folder, c.project_id,
|
||||
c.system_prompt, c.is_archived, c.is_pinned, c.folder, c.project_id, c.workspace_id,
|
||||
c.tags, c.settings,
|
||||
COALESCE(mc.cnt, 0) AS message_count,
|
||||
c.created_at, c.updated_at
|
||||
@@ -388,7 +390,7 @@ func (h *ChannelHandler) GetChannel(c *gin.Context) {
|
||||
WHERE c.id = $1 AND c.user_id = $2
|
||||
`), channelID, userID).Scan(
|
||||
&ch.ID, &ch.UserID, &ch.Title, &ch.Type, &ch.Description, &ch.Model, &ch.APIConfigID,
|
||||
&ch.SystemPrompt, &ch.IsArchived, &ch.IsPinned, &ch.Folder, &ch.ProjectID,
|
||||
&ch.SystemPrompt, &ch.IsArchived, &ch.IsPinned, &ch.Folder, &ch.ProjectID, &ch.WorkspaceID,
|
||||
scanTags(&tags), scanJSON(&ch.Settings),
|
||||
&ch.MessageCount, &ch.CreatedAt, &ch.UpdatedAt,
|
||||
)
|
||||
@@ -484,6 +486,13 @@ func (h *ChannelHandler) UpdateChannel(c *gin.Context) {
|
||||
}
|
||||
args = append(args, []byte(*req.Settings))
|
||||
}
|
||||
if req.WorkspaceID != nil {
|
||||
if *req.WorkspaceID == "" {
|
||||
addClause("workspace_id", nil) // unbind
|
||||
} else {
|
||||
addClause("workspace_id", *req.WorkspaceID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(setClauses) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
|
||||
|
||||
@@ -207,6 +207,22 @@ func streamWithToolLoop(
|
||||
})
|
||||
sendEvent("tool_result", string(resultJSON))
|
||||
|
||||
// Emit workspace.file.changed for live editor updates (v0.21.5)
|
||||
if hub != nil && !toolResult.IsError && workspaceID != "" {
|
||||
if call.Name == "workspace_write" || call.Name == "workspace_patch" {
|
||||
var toolArgs struct{ Path string `json:"path"` }
|
||||
if json.Unmarshal([]byte(call.Arguments), &toolArgs) == nil && toolArgs.Path != "" {
|
||||
hub.SendToUser(userID, events.Event{
|
||||
Label: "workspace.file.changed",
|
||||
Payload: events.MustJSON(map[string]string{
|
||||
"workspace_id": workspaceID,
|
||||
"path": toolArgs.Path,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect for persistence
|
||||
result.ToolActivity = append(result.ToolActivity, map[string]interface{}{
|
||||
"id": tc.ID,
|
||||
|
||||
@@ -101,6 +101,34 @@ func (h *WorkspaceHandler) Get(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, w)
|
||||
}
|
||||
|
||||
// List returns all workspaces accessible to the current user.
|
||||
// Includes user-owned workspaces and those owned by the user's teams.
|
||||
func (h *WorkspaceHandler) List(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// Fetch user-owned workspaces
|
||||
userWs, err := h.stores.Workspaces.ListByOwner(ctx, "user", userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
all := make([]models.Workspace, 0, len(userWs))
|
||||
all = append(all, userWs...)
|
||||
|
||||
// Also include team-owned workspaces
|
||||
teamIDs, _ := h.stores.Teams.GetUserTeamIDs(ctx, userID)
|
||||
for _, tid := range teamIDs {
|
||||
teamWs, err := h.stores.Workspaces.ListByOwner(ctx, "team", tid)
|
||||
if err == nil {
|
||||
all = append(all, teamWs...)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, all)
|
||||
}
|
||||
|
||||
func (h *WorkspaceHandler) Update(c *gin.Context) {
|
||||
w, ok := h.loadAndAuthorize(c)
|
||||
if !ok {
|
||||
|
||||
@@ -454,6 +454,7 @@ func main() {
|
||||
if wfs != nil {
|
||||
wsH := handlers.NewWorkspaceHandler(stores, wfs)
|
||||
protected.POST("/workspaces", wsH.Create)
|
||||
protected.GET("/workspaces", wsH.List)
|
||||
protected.GET("/workspaces/:id", wsH.Get)
|
||||
protected.PATCH("/workspaces/:id", wsH.Update)
|
||||
protected.DELETE("/workspaces/:id", wsH.Delete)
|
||||
|
||||
Reference in New Issue
Block a user