Simplify codebase per audit of 48e3457
This commit is contained in:
+7
-2
@@ -148,7 +148,9 @@ paths:
|
||||
/api/me/lead/notes:
|
||||
get:
|
||||
operationId: listLeadNotes
|
||||
description: What the calling user's lead agent has noted about how they work, oldest first. The lead keeps them with its remember and forget tools.
|
||||
description: >
|
||||
What the calling user's lead agent has noted about how they work, oldest first. The lead keeps them with its remember and forget tools.
|
||||
lead.note_saved (payload: the LeadNote) and lead.note_deleted (payload: its id) events tell the user's clients of changes.
|
||||
responses:
|
||||
"200":
|
||||
description: The notes.
|
||||
@@ -173,7 +175,7 @@ paths:
|
||||
operationId: listSavedCommands
|
||||
description: >
|
||||
The calling user's saved commands on this server, by name. Each starts a terminal in a task that runs it.
|
||||
command.saved and command.deleted events tell the user's clients of changes.
|
||||
command.saved (payload: the SavedCommand) and command.deleted (payload: its id) events tell the user's clients of changes.
|
||||
responses:
|
||||
"200":
|
||||
description: The saved commands.
|
||||
@@ -1426,6 +1428,9 @@ paths:
|
||||
/api/workflows:
|
||||
get:
|
||||
operationId: listWorkflows
|
||||
description: >
|
||||
Every saved workflow. workflow.updated (payload: the WorkflowSummary) and workflow.deleted (payload: its name) events
|
||||
tell clients of changes; a rename sends workflow.deleted for the old name, then workflow.updated for the new one.
|
||||
responses:
|
||||
"200":
|
||||
description: Every workflow's name and description, by name.
|
||||
|
||||
+14
-13
@@ -22,6 +22,7 @@ import (
|
||||
const (
|
||||
defaultReadLimit = 2000
|
||||
clientByre = "/Applications/Byre.app/Contents/Helpers/byre"
|
||||
clientProperty = `"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."}`
|
||||
)
|
||||
|
||||
func (m *Manager) clientShellTools() []mcp.Tool {
|
||||
@@ -46,7 +47,7 @@ func (m *Manager) clientShellTools() []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"command": {"type": "string"},
|
||||
"timeout_ms": {"type": "integer", "description": "Kill the command after this long (at most 600000)."}
|
||||
},
|
||||
@@ -66,7 +67,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file or directory, relative to your worktree, absolute, or starting with ~/."},
|
||||
"dest": {"type": "string", "description": "Its new path on the client, absolute or starting with ~/, e.g. ~/Applications/Byre.app."}
|
||||
},
|
||||
@@ -84,7 +85,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file or directory, relative to your worktree, absolute, or starting with ~/."}
|
||||
},
|
||||
"required": ["client", "path"]
|
||||
@@ -103,7 +104,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file, relative to your worktree, absolute, or starting with ~/."},
|
||||
"url": {"type": "string", "description": "Instead of path: an http or https address to open in a new tab."}
|
||||
},
|
||||
@@ -125,7 +126,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"op": {"type": "string", "enum": ["tabs", "navigate", "js", "capture"]},
|
||||
"tab": {"type": "integer", "description": "The tab's number from tabs (default: the tab the Page view shows)."},
|
||||
"url": {"type": "string", "description": "For navigate."},
|
||||
@@ -145,7 +146,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file or directory, relative to your worktree, absolute, or starting with ~/."}
|
||||
},
|
||||
"required": ["client", "path"]
|
||||
@@ -161,7 +162,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file or directory on the client, absolute or starting with ~/."},
|
||||
"dest": {"type": "string", "description": "Its new path, relative to your worktree, absolute, or starting with ~/; parent directories are created."}
|
||||
},
|
||||
@@ -178,7 +179,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file, absolute or starting with ~/."},
|
||||
"offset": {"type": "integer", "description": "The line number to start from (default 1)."},
|
||||
"limit": {"type": "integer", "description": "The most lines to return (default 2000)."}
|
||||
@@ -193,7 +194,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file, absolute or starting with ~/."},
|
||||
"content": {"type": "string"}
|
||||
},
|
||||
@@ -208,7 +209,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The file, absolute or starting with ~/."},
|
||||
"old_string": {"type": "string"},
|
||||
"new_string": {"type": "string"}
|
||||
@@ -223,7 +224,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"pattern": {"type": "string"},
|
||||
"path": {"type": "string", "description": "The file or directory to search, absolute or starting with ~/."},
|
||||
"glob": {"type": "string", "description": "Search only files whose names match this glob, e.g. *.go."},
|
||||
@@ -240,7 +241,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"path": {"type": "string", "description": "The directory, absolute or starting with ~/."}
|
||||
},
|
||||
"required": ["client", "path"]
|
||||
@@ -257,7 +258,7 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
|
||||
` + clientProperty + `,
|
||||
"tool": {"type": "string", "enum": ["screen_list", "capture", "ax_tree", "act", "browser", "chrome"]},
|
||||
"arguments": {"type": "object", "description": "The tool's arguments."}
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@ func (m *Manager) takeBounce(ctx context.Context, l *live, final string) (bool,
|
||||
if !asksQuestion(final) || m.isPaused(l.id) {
|
||||
return false, nil
|
||||
}
|
||||
first, err := m.store.FirstPrompt(ctx, l.taskID)
|
||||
first, err := m.store.FirstTaskMessage(ctx, l.taskID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
+111
-109
@@ -5139,115 +5139,117 @@ var swaggerSpec = []string{
|
||||
"TgEv4LPwCQdiYRnreAsZ7+ZfCpE2KmPCRnL7a/Wwpm3lJjV8WZaVvhm7ZG/meT6EkTlFFn5xC7Ny+ygU",
|
||||
"A+e+eqeNOokRwdMuAtdZzFC+KRiady1v54Lih+H3IH6Y/AY6ltSkS+0qKQ1LIcSVCIoGopTo4G+HjdG4",
|
||||
"3Ogr1dym8upotWmd4ebWTIUvM2eJNQQm3BaeUbDpQyASdv1PiUIFb5ndx/mRH3hGUFmaVJkSsIiVUy85",
|
||||
"EtRRKj3O5USODV3pMAxG1WEeO5xT91hngp/UipKOsLYixFQ1PJ+oyYb8fpQOtXRXgEB5Dl1/Lr+fw0Kn",
|
||||
"shEPZWFGnicfP3Y3+MCKhwJAb5CHOQH5EPD7S8scfTgeALfPcyxOPvAvVuUTJo734QU+H+3DPn0mfVY/",
|
||||
"kFI7Tf2eOu0Skf1mUe3JPxCq3VfttlEnHDF0nCJ8pu/+kDJjAC3N8nMMyPmOnBklDHhBM3jNOR8eAgN6",
|
||||
"Q/xTXruXdrnb4/Z7bPCA66QBCgv9ZS2LaOIwbmUrjSKf7V3uqdEw3SUloEKc0cem6+tRKmmK6WC5tNjI",
|
||||
"ltyB4/NjWwaJL4dmk+DPnPkQSb4kcQELxT4DWYEfhM+YN+tr9SuMV6XY1Ou1bajqHJbMwxpraP2Kxej8",
|
||||
"f3n6KKzzMhyZspp/evUr2s6yNxz3HQtFKxNfas8ZtI7m4g1MAv17LsDQ5lMtO5xmsu1B9zJGlk3xkD8q",
|
||||
"We80aA3MPtTbZ7I+cRnvrquNvKFkq7978qTanXr1Lw+IzgiQN5jXYYzSf8TAC5w7//uYrzHxuydHldg2",
|
||||
"rUdXO3jJxZG6+lGFHZ2X8DZ6Au7wtEYnwuRUHc0cJWzmiiCpRJSkx7E2fCFdKAvSqWhwXuAKHsZBuU4l",
|
||||
"3smelcSYzQccEhH5C3j3mj7+kSyID0EEeYS7GBR+8/nEa4A2V/Gfook9+OcAvQYgBiusS7DF6uefcj0j",
|
||||
"Qhkb1DQdnLomO/Th6jJYtRIrRrOb1i2n2OzZP0VaY6qauOk8gJ3aYDh7TOixUrBk2/j5JIn6CWf/JUTc",
|
||||
"ONqh4i3CdT4J8ZMP8N9B8lYa+evLWjTn+zlQx+Uzdt/97o2FZunzIWY9oxIv5DMci1pyOVNTR9afciXf",
|
||||
"hGmMOldfMCyuTxIO1JvwBIe4leeu8/v0peDlC2JQ0j1mFSvx8qqoWD15nDsZFOXyxKOuL28LqlbDvg9o",
|
||||
"YSEPYriopgSscb6/hyH543H+KUWtrTTqOMlbO8WVXFtOYoumUr6onjYD0YW019k3t5QqrzV4r2OEAbzK",
|
||||
"eYNbNO1mXYJVLfEMdI6X0ghIk04KcYxGZ6TysYYI3iTWNoLWcVFCpnMV3kijHlJy6vr/iv63G3WC+SWP",
|
||||
"ORnmboVIl6cwVZJm8TMW3tSmYme6MbXVb9ZclNcF31XCf0a0lRLtnYkLG9ac84XYXdjIy8up854ydD7M",
|
||||
"JmUJQP85TvcnCLhcbO0YI2N3BjxTw7fY7hOBJOtaU6KGN71sB4dUjRxnCxhX70OnE6q7Gc0qgxsIGFGH",
|
||||
"BIZFLFqdsJdEfCBNEXmZ/4rtehcon49pcAF/8CY2+iIh8zTYIWzBs6aJB9wXV3XygX+BI4Zsax2O82JT",
|
||||
"+xm/9PlncRxPdAqnklWywjoLwDowjSKtSKoWKKwplBh5hBpd2Fmq+XfWo0o4hEfKJOINyKuZ8wGtcg5R",
|
||||
"mjirlH0/7zzW4of0oygQp5xWsXmXNBbLcFkHDut2EyeCqY18FYtyrnToVccY3W7P4LOY2OShbjiachrl",
|
||||
"C9PQhOm7yWjctS9FSYuHp9Zu2iPYqCwOgnE12ovjC618VBsG1PokO7IJ+BL+qsUcEEM6JRp1GYRtwxmg",
|
||||
"il4Z1JkqbquN17UaoJ1TW+s1zgHzl6CDk1NCm2XTxjrvWKSl6HP3g4p07oV2Y+Xf4CQ30q+PvYJGdJIu",
|
||||
"vG3aoChDVCOJXtvpKTprw7h6YwzfioWCSmpCplA7yc+D4ewL7eBG2BHKkzDhkY/79eCoWz0cKd95KiAK",
|
||||
"aEdEtFOrtpGOg4XyczEX70CAQNdpoNcqJpiSwuu/s9B5JijABr+PLYF48k7uxmKoo/pl0HgPot7p3nw4",
|
||||
"kwyUleWOJjAXwPxPi6qYzGAaV1N+kgKqwuRYPX8P5BhkJKkwHwnS4vP/+UMllmvr6TpQN0EZdLneidev",
|
||||
"cCEHGWceAAH/UbNY+Per/3Kzab56EgzAr5gE45/2rDl5fa9boSI7ahVTWuSpQbKDooPvDguGCa1UgLCN",
|
||||
"t/KaekKXu52n6K28/qe7HP7j5D/+jaMH4yimMf02xOFBlZFYxAwe+gF2xQgu3+VHQdsqBtqYvggce9gl",
|
||||
"Axdlz7f44b+Fz29F+IQ5122jdtvOMiSJ7SvhrTXKB0IS15poNotNMn/k9KjkkHwHL+Q02S/igcyjHWxF",
|
||||
"i7Obf1OkaiqGiTwWvJBp4qxIGqlB8e7kMCXZVy+ls9/f9EKKUPK35lYP5QDL3X8VP+uELdPxnQlIX/XI",
|
||||
"o5/qgcc90457NIg3FqsjYZElLM2PdwUzSWf0gg93SnUTdeu3fL5jTqh48C9VAEU8xP4Se8bdTZGBZzip",
|
||||
"d7iML0EIKGz3MCJQBl0VNb5EIv+h6EPcwUeeleO9QhUdr9CtvK9LZ2sv7CslKo5q8RTLjjmesEFnZFwm",
|
||||
"6oS54S+JceneU/WgruSXp16jabGYuA27xM18GPIDXf/LBpbvIzpc/H2S5uBJhI3O6rpHdW1MmDCNYtrE",
|
||||
"tEFcW2sknGH1+Qdk+rD/iS3AtWHGNg9478WlbLzq7DuF+vbGRkDM/3EEH9jk7nr5ihwN3jiU0yKiE+xB",
|
||||
"JRyyK9GJGZ5FN1KY+4IaZaa7RKP61fkjlzTIttEZ7jA0lAqi9CzMa+m7xJMY8VzM/SMd4isGTj8QrUqV",
|
||||
"7v+dA+NQwsZC78kH+kFWCgPY5E8+aFOrm4+H4X38/k5oX5VzpeH4EAxiPRU7ie5WOMYjH5vE0npPktao",
|
||||
"Pyec/yET6vnF7wlikSFzCZN1zcU1ZZwUlQSprYnzxSBYr0zdhUF3KRMCVFK7ERZz+qDdklqVA19ohHMu",
|
||||
"jvYg8S/ZEF8nWp60KVPHiUD6xe79JP+efIg/R87IJc/iXAQ9Y2ZOB+YAkRKX5Mw6eiVX5YjSXNL8uh7O",
|
||||
"HTA+i2rvbcxU3EENI3CwhhU71yqYdRtUFiEBNU0PltY5ZPVbk9affDFp/YtL6tEF++QD/4Lbhd3PD7tU",
|
||||
"0nefh5l6RlVSFWVc4s4fRW6IeCvIemYvL8XjlZVNhcVq2JHeBxkUPJG1oAysvSw56BveKH8EHr6K842h",
|
||||
"CZ4ah84pnZIIk+99zRWiUJfCr63pGCpksZhzS/xanGQpQVoVP2f8VnVX3lI1XFAVso6n9ZczguM2Raf7",
|
||||
"B8rZS4N8u3FGEc64jdeSBfJ7UNX7smcTR8gEbVr1Vc5QJ5CkNIDIg0kqQl5TllsuOhuVLL2YFCpTX9K7",
|
||||
"ZOqO3aJxzD/a65dOXAQOs2rdIV9a8145L7ts+TANZWoqkpxq4thrk3qETH9H/er0fMr6Rw/wAy2rXpma",
|
||||
"o+cw/+IgYeKaczSqekFAw6Rv8ZEMQ3EroxmcWQcdK1D4qphg9VbP+Q0j3DXQmI0yIabXwU96dTnj92Ui",
|
||||
"QLB8WCpwj2jDf4tuU7RBw167dvt1LtiXsdr8snVOGY5hHeHVqzjJHLG+IplPQEManzKBfl0q3wth/tIb",
|
||||
"eR4TNeEZwNsvZW9NsbZR1ZdxNcBYpTRizLt09Av60T6Gc1fDUOhfW9WqVK1sGQs+c6YxzA/QBUdDwBMY",
|
||||
"9FDRLruQxQmUO+8I879q0DSCfz1iYv4hyNpWtv7r8DtvYOSkf+30prYNHd1gzc5jr5TIt2KO866PCgn2",
|
||||
"4cUk+XvyRREjpeOk2X5dsueUbzfq691ekqBw1hWTivlHgTbVLevnRul9jb0e7/JbXMy3ts1Mf2Pdxy+0",
|
||||
"21D01qiw02CGIjZWZ751inlx/k4YLBHfy0pJSSLhTkglm1ghC8ZU5TBqlnw8jE0OHpXwrIHdbnuiNlSN",
|
||||
"WkojvF7RU4OViLqUIwQ92Hh50Tlj8gQnHCjf0dsHNdPxEJMpFmqFtall6BdKRbXkyQf4b0/JvmRgfrAV",
|
||||
"THPhGQf+2Qu94MrvZyzsA+/kwkpXH0a17jPq5O1kPdr5hKRi+NGpAWaTuz0pd8p2QixmBq/n0GN0W4sZ",
|
||||
"aJh9h1fQ60TN/V4uHGQrWK5EvVrMJYEHhdrEGHd8NcHIlc4PLO+d/T3C9mG4NuwbhrkL3/bdhIuR9SRB",
|
||||
"OA5kj1AN3yirNcBh9lScpNAvs5p2JvehSQqcxxT9ie7f6MdSV6I19AuB0ZrgJJTcrwTMSy0RieYpZu+I",
|
||||
"ih5BZxVlKskqRyhSMR1fSK8SOgJXlumSduuNok8OFWJEAWal3ytDapq4DL540HrYZSnn6hJYbacrDIFz",
|
||||
"gKXrqHDiV48w7DAoWe9fCNX1BMilNUzNnNan3mvb+nwsnAMuyAe1xdm4Vk3eSf7qOW/2Hq/+Z3FiwcZl",
|
||||
"0ER4cXEfugVVqYalpJD+KOHFxGprCCoXP9kgZNPY6wiWuJipCADcs9mdrMnPcgiZU06VEbFGpmzz2vT3",
|
||||
"TjjJaTwk4TnWW4roMTlB/nq208A8mmQPoU77QC7uNukr01+0P2Atly6PCSXNpDXqaHLCQW0/tQbRJ3qW",
|
||||
"MRIWrv43yh2j+5MPMnxrtRu+MHMB9X928s2t0ZcaxBN9eQkn0hoOzbtQ4VopA3e+9HQtdx6Oqx4lmE9R",
|
||||
"ihcw+mHZDaVXd/X/KPUDU/wc/dw5aGiiH9vUi68aRg3RqLgNUx66MizX/+pn5BvNODDkke6WY4AO4OdN",
|
||||
"MDCc0b9zC/zrHBI0zH55OZXK22XIxzIlHExVw/5zRj3A0WunQ3d2YoKiKuqtY22Rrjr0wACBWYG1YetF",
|
||||
"L02rjok4jSK7LrqzXmuPBl1vbQoWikqyzIMDWTJj6Rt6IWsSd8RGSYM22zNRO7ulFLaYfxQ547OYHm7o",
|
||||
"FwJ5QLFCkg48ilOQ782XaYepo7ATddDQPQrZTEm6DzszPIk33BkJp1UEK37I0S1ObUAjWDBCR9Mzj9ZV",
|
||||
"97tQMHgaCQWVtYw2aiiMBp/6MyrPjt1QmMMSWMx2Ky6lbkgcJMoZ1undRJEnGKmskPrNF1NIVZyZOJri",
|
||||
"aU0Mhq/iKTI46Qck7mZ8wGrH+IpVM1J49eswbyglze7ny24kpwkjzStXt4WQKnBo6Hc99GAihCLxi9wf",
|
||||
"kCetYl4TU/MlGQdL4mH0ocQVRvEzvh10KYN4DF15KywrqfAV+eiid8cZ5x/nI3Cl1DZm6ETlM1Z4nYra",
|
||||
"AjS5S/rvz1h7FlO+lLN/P33y4Om/D4oxQ8gcEmRGOcFxRXEfsqTgT46+ffXvN5nZB7Dzs6f1KTGP/87o",
|
||||
"88/CIN477Q95o72W7qq219yMHLfpCxmCXK43VLH8kAxBAzz7fHmB4FT8OynQv5MC/TMcV5Qtvrw0lzKx",
|
||||
"8CHFaSyCDk06vktrLrUDhQk+Zp1Kll4eLe4cA4WSFHdzrE3VOdgEL7R5rwOeYOgblOHsw44dF53thQHE",
|
||||
"appYxdL/2kq/npCptAkWBdFo6IBu6z6nOlijNfuiw9uwRn3QxW1mgo15mp7b49jgGBo4EIEcElB0KslM",
|
||||
"W+i3zK19JbYtLkNzjQuogV6JS+nD8aV11zJWWeomljJbBhuzi5uehFkSLQuiJK6+Tp59HAWKr7TpZ87O",
|
||||
"hMRkGErFOKLV6DGBk7xqFui4LLwKR3mcKkrLxnY9aAOjrJzyfjhqNJsxzAYKgVJ5DdpJiFvWNyx8CHQZ",
|
||||
"aU3QTbdyzA7hR3GwY2dGVpel2UYJGdbNZsMgsVgHGWqS/zaJyJjdnUDGT7rkBAxTVROywqmgPgZJDBi6",
|
||||
"pwQabJFULTAjf0X1/HmOQTymDRD6kp1bYpWaoy4lepwfASKd0zpWMM9nqU1/6D+8fPYiBr0rU0fThL3s",
|
||||
"N+M6QIw3C1jkghdIeNFtijkq+sgniA22BXZFh3w7eJAhJOfipWacxj2CLspakIJ2I9pw7bXhnUZBbxHj",
|
||||
"P+G4wJiM2LcqnOYROkJHrYxKJyPvoHc4NrJWFSmptI+EJWWjwIe0hKpz0OVu+nSFa2UlyZkLW/ik5uqC",
|
||||
"bPvGUe1Z+ZFV3OJAhgH0zzL1Gu0C6Ma2kbrKXEFgbMiizR9/9+Q/j0RrGuV9nt8r65whDQL7AgOxHvPW",
|
||||
"gQ6LT5OxHOSeg/OoGisiMbyDDzUOh7vgS5kMgOQ65W3zntRut3jXNXoZfDo0RjTS1B2FFd6KS8kXAN8k",
|
||||
"doET5esmv14eZ9MDZjp/p71oDTlq1Ki76CLEbj1VRM6VfRytv0KZWjpFVDiepvzOPGWtR3J7RNLAG9tD",
|
||||
"X2oHH/q5eBVQW+cAej5IxBhMG3+7uLRugVMfbiuTUHM7wEieZkaGyO8Bm+v8aimXxXKrT82EUs7Oj5kt",
|
||||
"qtk2e/RhlmPUmEPCyRCFzJXJ/YN4hxOYeVuwo+nQVl/N0kkYT+fHSMxzZIRnUd1FSAQf98YCmt/Fo4+H",
|
||||
"zDBzwqETOa/ORwGbnuIl0GismhKoFPj/9huxXEsnl0E5vG2JSSUrVHeu52MZoZrR+/EE4APxODvCR6dx",
|
||||
"QtaMElH2j/mc74XTeFmmDwoX7xmSm/59DjNVBvRvf8KJkLSpTJj9ZbiEanZzDE2P30sH3Drw8BGdYWWv",
|
||||
"6fPsyRvu6WO5LMSXizvYpScnwGW68AqpT4UXerD0NKv7GG9L68TjhMtHeLFyLcBvOWyhJJkd4+KOcyJR",
|
||||
"ThoAN1nt5DVQUSIdo1srxvfx3XstNbrOXVrHReGg0Vmi1YllLKStk2apGhzmGQzACRe+kntv5IEAT64Z",
|
||||
"DObbV/hGWfXLC98piL3HwCSufhdR66hY5jY5yc2wE+9KGcQesxL8U9WkiJ6Lcx46m04eWo9nOdX8nzLE",
|
||||
"7pWtJm/7V189cpBhXg4P/8ZJ1JZoxp60idaFArqpmy0mTUQyRX8soGmVuc1IkglRP1z3Ai2gpTAtFrHs",
|
||||
"gj3yQI9KWDCKbxu4wv/w7t2baiiXa18KOJ8y1L2Ja/0yJYk4hc2h6RVpbt8+0UORANJCbduwJw3OO3ml",
|
||||
"PBuGY/BUdAR+TCk4uiB8dBPmRmj/O8qLOmcSGqiwYjIYCmXAT1uTf9zRk8R/x5HXWNBZGUzsAA3AAldA",
|
||||
"mXgf5xGjh0QAxHE+7TL7RpTO1YdyTi3c/J097TdqT2HYl0yFfyok0JbXP1JZctZdye32kSd3B2S7khos",
|
||||
"3DbKr5UCMZ+mldm3UCfbWFlzxoe4S2IpHbq1SOGlqS/sjWAb5PG5WrZOh9vjN7bRS4pKixXSt8pEnSRq",
|
||||
"dDiqjR3amluxlJjnYRkiGJ69eRVTUsC0ldth/frcyfy/skn4HzCP/zduVkL9zc4kZ9pvtPd5gD5FGBb1",
|
||||
"P8gFtquV8mHBN3WKwYx3ubGisWalHH8cbPctf7PgPlR9xq5kc2qLCqjkERTvhlIOPZ73W1zfVxB4Itzq",
|
||||
"b/+uL9SfLiSv/9arQJfyDw1rYn87W7EvG8cw4dFEFF4Xy6lDlAc32zBOWTSZ0v5B8/1w718n388hQl3m",
|
||||
"NfyPIcmlKicnH8zHE3BrfDC1RJkpNHdPsnpocjHyrD0gW1gW6kfq5i5NGOok9uQK63VAbXsZw/DwCB+s",
|
||||
"TWm8+wF50wnEuiRgXS6c7hBS5Ogg77LwrFnRYZc7NpUuyFJ7dXOKNoQuFhnXFPuXAXPOxsQGJW7xe+uu",
|
||||
"zruYxn+n4Np/DrOK0g+qGDwoQ3G56nN2YWTln7Xy7Hfq1a/gwQ1u3RFHGo6axeh9NqMntOKg8ah4BtYM",
|
||||
"K0g7bK42GJ9BcEF2rYLz0XnGoPw+1u5Jd5VV3X6oiygb4YvXrL9vUtUBygXlNtrIxn8FL7Co2I1zmOBH",
|
||||
"2FAPRFmaeqHrqlRNnworcSOkibfbzuGlU+X4tWoazsNS33ZuKmkSATVPOAJ19ohC98bOCBTQdw4dTqes",
|
||||
"l/7qHXf9UGnrufuvlro+rW+KiY6g/coxLQnXTz7En3u0kH+QZkWELVsGIBvu+Zj7het0tOP7SAB+dT+J",
|
||||
"bkLP9Yu6OLfLKxXYbZq8tjLPqUjAI0DQhqP/Tu2WtgEthnD2mrVRpCNjDoMkg+t4S6TNpRtiqQzol7ZO",
|
||||
"G9T7hBhjZ9uwbVmGoDJJEATWwAMOToieIzQmFULi0VBpWGjKylyYOnqWBYiFSC/Xtqkpf0w6Ivrvncdn",
|
||||
"pAXqRkefUlone6pdE9WplfjuyZMniVBAc8Dk0PoU66ek3+kvnuHDAS7jsAE7SezO0JyJeB/Yzft3OtSi",
|
||||
"PX3ydIx459c6RGQDsHRouHU22KVt/jXUYmUZpyM5n0W/Ess+nbjW7CtcRi6vfPbjh1hEiqQjLkamvdDk",
|
||||
"xwV2X224dgbKSH9XnZSATsZUZkoHTgwWw0DdbexNIvVRm22AazhKU9A5P/Y8OlEGT91ao1KcLohmc/Hd",
|
||||
"k+9YNuk6jmmKXTuRfvwHlSpdvm0fNHNePsyUiwLMMu3Dt6i6y5nA6dRNKQI4hjZ310djV9oQPUUvXsCL",
|
||||
"yL9VtLP8cd+TGkgxYE23715Yx5HMHWfo5+yyyDraoXO9Ro7vwllZL6UPMZ52K2/BppJyK3HrSI1BAzkZ",
|
||||
"mZlA8pBq3mwi+5gn30u9Fw/xbvXqL6nVl9CvxtFYLDpExUpoFRfDjDaSkqxd5xNQhMDJB3g3YONK5U96",
|
||||
"BXYPLH+SUa1UyoVwyUmDypV3kvxpJJNJoJdZEPDSbm8/hbWbJGtfgqa9SLfAFHZmW9ddGQ9E4fC/z6Hp",
|
||||
"iFpCNEVy4ZjezZgcC7I1kQ4O0FP75H8Kd+pTEax4+uRJz/80w4qo8SDs6I0zxBMSdCmOHVPEwnh+MK9g",
|
||||
"cRqxYOim9ej8Li4Uiq6G7ulYi67n/H4du+89oEWR024xiWKbI90B/CuMsD+ry+cXhKfQ9suV0BmRwPKp",
|
||||
"8QGDu+K+fmvKyY8f//8BAA==",
|
||||
"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",
|
||||
"atmPH///AQA=",
|
||||
}
|
||||
|
||||
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
|
||||
|
||||
@@ -104,11 +104,7 @@ func (s *Store) StartDiscardTurn(ctx context.Context, taskID, sessionID, actor s
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `UPDATE agent_sessions SET state = ? WHERE id = ?`, string(api.AgentSessionStateRunning), sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(sessionEvent("session.state", projectID, taskID, "", sessionID, actor, map[string]any{"state": string(api.AgentSessionStateRunning)}))
|
||||
return setSessionStateTx(ctx, tx, emit, sessionID, api.AgentSessionStateRunning, actor, nil, projectID, taskID, "")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -152,15 +148,7 @@ func (s *Store) EndTurn(ctx context.Context, sessionID, actor string, payload ma
|
||||
}
|
||||
handsOff := continues && role == string(api.Implementer) && state == DiscardNone
|
||||
if !handsOff {
|
||||
_, err = tx.ExecContext(ctx, `UPDATE agent_sessions SET state = ? WHERE id = ?`, string(api.AgentSessionStateIdle), sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if payload == nil {
|
||||
payload = map[string]any{}
|
||||
}
|
||||
payload["state"] = string(api.AgentSessionStateIdle)
|
||||
err = emit(sessionEvent("session.state", projectID, taskID, owner, sessionID, actor, payload))
|
||||
err = setSessionStateTx(ctx, tx, emit, sessionID, api.AgentSessionStateIdle, actor, payload, projectID, taskID, owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+4
-11
@@ -995,15 +995,6 @@ func (s *Store) TranscriptEvents(ctx context.Context, taskID string, after int64
|
||||
return s.queryEvents(ctx, query+" ORDER BY seq LIMIT ?", append(args, limit)...)
|
||||
}
|
||||
|
||||
func (s *Store) FirstPrompt(ctx context.Context, taskID string) (string, error) {
|
||||
text := ""
|
||||
err := s.db.QueryRowContext(ctx, `
|
||||
SELECT coalesce(json_extract(payload, '$.text'), '') FROM events
|
||||
WHERE task_id = ? AND kind = 'user.message' ORDER BY seq LIMIT 1
|
||||
`, taskID).Scan(&text)
|
||||
return text, err
|
||||
}
|
||||
|
||||
func (s *Store) LastMessage(ctx context.Context, sessionID string) (api.Event, bool, error) {
|
||||
evs, err := s.queryEvents(ctx, `
|
||||
SELECT `+eventColumns+`
|
||||
@@ -1324,12 +1315,14 @@ func transitionSessionTx(ctx context.Context, tx *sql.Tx, emit emitFunc, id stri
|
||||
if state == api.AgentSessionStateRunning && role == string(api.Implementer) && landing != "" && landing != id {
|
||||
return fmt.Errorf("%w: held while task %s merges and continues", ErrStateChanged, taskID)
|
||||
}
|
||||
return setSessionStateTx(ctx, tx, emit, id, state, actor, payload, projectID, taskID, owner)
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `UPDATE agent_sessions SET state = ? WHERE id = ?`, string(state), id)
|
||||
func setSessionStateTx(ctx context.Context, tx *sql.Tx, emit emitFunc, id string, state api.AgentSessionState, actor string, payload map[string]any, projectID, taskID, owner string) error {
|
||||
_, err := tx.ExecContext(ctx, `UPDATE agent_sessions SET state = ? WHERE id = ?`, string(state), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if payload == nil {
|
||||
payload = map[string]any{}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (s *Store) PutWorkflow(ctx context.Context, name, from string, def api.Work
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return emit(api.Event{Kind: "workflow.updated", Actor: actor, Payload: map[string]any{"name": name}})
|
||||
return emit(api.Event{Kind: "workflow.updated", Actor: actor, Payload: ToPayload(api.WorkflowSummary{Name: name, Description: def.Description})})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -506,18 +506,7 @@ final class ChatModel {
|
||||
store.focusTask(task.id)
|
||||
return
|
||||
}
|
||||
if team {
|
||||
if images != nil {
|
||||
throw ApiError(status: 0, message: "The board takes text only; drop the images to post.")
|
||||
}
|
||||
_ = try await api.postToBoard(path: .init(taskId: id ?? ""), body: .json(.init(text: text))).noContent
|
||||
return
|
||||
}
|
||||
if let sessionId {
|
||||
_ = try await api.sendMessage(path: .init(sessionId: sessionId), body: .json(.init(text: text, images: images, model: model, effort: effort, sendId: sendID))).accepted
|
||||
return
|
||||
}
|
||||
_ = try await api.createSession(path: .init(taskId: id ?? ""), body: .json(.init(prompt: text, model: firstModel, effort: firstEffort, images: images, sendId: sendID))).created
|
||||
try await store.messageTask(id ?? "", text, images: images, model: model, effort: effort, firstModel: firstModel, firstEffort: firstEffort, sendID: sendID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,79 +28,43 @@ struct NotesView: View {
|
||||
|
||||
private struct LeadNotes: View {
|
||||
@Environment(Store.self) private var store
|
||||
@ViewState private var list: [LeadNote]?
|
||||
@ViewState private var failure: String?
|
||||
@ViewState private var loads = 0
|
||||
@ViewState private var project = notesProject
|
||||
@ViewState private var off: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
let list = store.leadNotes
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if let list {
|
||||
if list.isEmpty {
|
||||
hint("Your lead hasn't noted anything about how you work yet.")
|
||||
} else {
|
||||
let groups = [("", "Every project")] + store.projects.map { ($0.id, $0.name) }
|
||||
let current = groups.contains { $0.0 == project } ? project : ""
|
||||
let shown = list.filter { ($0.projectId ?? "") == current }
|
||||
let label = { (g: (String, String)) in "\(g.1) (\(list.filter { ($0.projectId ?? "") == g.0 }.count))" }
|
||||
DropdownMenu(label: label(groups.first { $0.0 == current } ?? groups[0]), boxed: true) {
|
||||
ForEach(groups, id: \.0) { g in
|
||||
Button(label(g)) {
|
||||
project = g.0
|
||||
notesProject = g.0
|
||||
}
|
||||
}
|
||||
}
|
||||
.help("Which notes to show")
|
||||
.padding(.bottom, 8)
|
||||
if shown.isEmpty {
|
||||
hint("No notes here yet.")
|
||||
} else {
|
||||
ForEach(shown, id: \.id) { note in
|
||||
NoteRow(note: note)
|
||||
if list.isEmpty {
|
||||
hint("Your lead hasn't noted anything about how you work yet.")
|
||||
} else {
|
||||
let groups = [("", "Every project")] + store.projects.map { ($0.id, $0.name) }
|
||||
let current = groups.contains { $0.0 == project } ? project : ""
|
||||
let shown = list.filter { ($0.projectId ?? "") == current }
|
||||
let label = { (g: (String, String)) in "\(g.1) (\(list.filter { ($0.projectId ?? "") == g.0 }.count))" }
|
||||
DropdownMenu(label: label(groups.first { $0.0 == current } ?? groups[0]), boxed: true) {
|
||||
ForEach(groups, id: \.0) { g in
|
||||
Button(label(g)) {
|
||||
project = g.0
|
||||
notesProject = g.0
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let failure {
|
||||
hint(failure)
|
||||
.help("Which notes to show")
|
||||
.padding(.bottom, 8)
|
||||
if shown.isEmpty {
|
||||
hint("No notes here yet.")
|
||||
} else {
|
||||
ForEach(shown, id: \.id) { note in
|
||||
NoteRow(note: note)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.onAppear {
|
||||
off = store.events.add { ev in
|
||||
if ev.kind == "lead.note_saved" || ev.kind == "lead.note_deleted" {
|
||||
load()
|
||||
}
|
||||
}
|
||||
load()
|
||||
}
|
||||
.onDisappear {
|
||||
off?()
|
||||
off = nil
|
||||
loads += 1
|
||||
}
|
||||
}
|
||||
|
||||
private func hint(_ text: String) -> some View {
|
||||
Text(text).foregroundStyle(Palette.muted)
|
||||
}
|
||||
|
||||
private func load() {
|
||||
loads += 1
|
||||
let n = loads
|
||||
Task {
|
||||
do {
|
||||
let loaded = try await store.api.client.listLeadNotes().ok.body.json
|
||||
if n == loads {
|
||||
list = loaded
|
||||
}
|
||||
} catch {
|
||||
failure = errorText(error)
|
||||
list = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct NoteRow: View {
|
||||
|
||||
@@ -108,24 +108,16 @@ private struct ProjectWorkflowMenu: View {
|
||||
let audit: Bool
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var workflows: [String] = []
|
||||
|
||||
var body: some View {
|
||||
let current = audit ? project.auditWorkflow : project.reviewWorkflow
|
||||
DropdownMenu(label: current.isEmpty ? "Off" : current, size: 11, color: Palette.muted) {
|
||||
Button("Off") { set("") }
|
||||
ForEach(workflows, id: \.self) { name in
|
||||
Button(name) { set(name) }
|
||||
ForEach(store.workflows, id: \.name) { w in
|
||||
Button(w.name) { set(w.name) }
|
||||
}
|
||||
}
|
||||
.help(audit ? "The workflow byre runs in the background on \(project.defaultBranch)'s newest commit to check the whole codebase" : "The review workflow whose reviewers review every snapshot of this project's tasks")
|
||||
.task {
|
||||
do {
|
||||
workflows = try await store.api.client.listWorkflows().ok.body.json.map(\.name)
|
||||
} catch {
|
||||
workflows = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func set(_ name: String) {
|
||||
|
||||
@@ -4,8 +4,6 @@ import SwiftUI
|
||||
struct QuickRunSetting: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var workflows: [WorkflowSummary] = []
|
||||
@ViewState private var off: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
let current = store.me?.quickRun ?? .init(projectId: "", workflow: "")
|
||||
@@ -14,7 +12,7 @@ struct QuickRunSetting: View {
|
||||
Button("None") {
|
||||
save(.init(projectId: "", workflow: ""))
|
||||
}
|
||||
ForEach(workflows, id: \.name) { w in
|
||||
ForEach(store.workflows, id: \.name) { w in
|
||||
Button(w.name) {
|
||||
save(.init(projectId: current.projectId.isEmpty ? store.projects.first?.id ?? "" : current.projectId, workflow: w.name))
|
||||
}
|
||||
@@ -35,28 +33,6 @@ struct QuickRunSetting: View {
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.onAppear {
|
||||
off = store.events.add { ev in
|
||||
if ev.kind == "workflow.updated" || ev.kind == "workflow.deleted" {
|
||||
load()
|
||||
}
|
||||
}
|
||||
load()
|
||||
}
|
||||
.onDisappear {
|
||||
off?()
|
||||
off = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func load() {
|
||||
Task {
|
||||
do {
|
||||
workflows = try await store.api.client.listWorkflows().ok.body.json
|
||||
} catch {
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func save(_ q: Components.Schemas.QuickRun) {
|
||||
|
||||
@@ -103,17 +103,16 @@ private final class ScheduleDraft {
|
||||
var dayOfMonth: Int
|
||||
var workflow: String
|
||||
var prompt: String
|
||||
var workflows: [String] = []
|
||||
var failure = ""
|
||||
|
||||
init(_ sc: Schedule?) {
|
||||
init(_ sc: Schedule?, workflows: [WorkflowSummary]) {
|
||||
title = sc?.title ?? ""
|
||||
every = sc?.every ?? .week
|
||||
weekdays = Set(sc?.weekdays ?? [.mon])
|
||||
hour = sc?.hour ?? 9
|
||||
minute = sc?.minute ?? 0
|
||||
dayOfMonth = sc.map { $0.dayOfMonth == 0 ? 1 : $0.dayOfMonth } ?? 1
|
||||
workflow = sc?.workflow ?? ""
|
||||
workflow = sc?.workflow ?? workflows.first?.name ?? ""
|
||||
prompt = sc?.prompt ?? ""
|
||||
}
|
||||
|
||||
@@ -137,13 +136,7 @@ private final class ScheduleDraft {
|
||||
|
||||
@MainActor
|
||||
func editSchedule(_ project: Project, _ existing: Schedule?, _ store: Store, _ overlays: Overlays) async {
|
||||
let draft = ScheduleDraft(existing)
|
||||
Task {
|
||||
draft.workflows = (try? await store.api.client.listWorkflows().ok.body.json.map(\.name)) ?? []
|
||||
if draft.workflow.isEmpty, let first = draft.workflows.first {
|
||||
draft.workflow = first
|
||||
}
|
||||
}
|
||||
let draft = ScheduleDraft(existing, workflows: store.workflows)
|
||||
while true {
|
||||
let buttons = existing == nil
|
||||
? [ModalButton(label: "Cancel", value: "cancel"), ModalButton(label: "Create", value: "save", primary: true)]
|
||||
@@ -180,6 +173,7 @@ func editSchedule(_ project: Project, _ existing: Schedule?, _ store: Store, _ o
|
||||
private struct ScheduleEditor: View {
|
||||
@Bindable var draft: ScheduleDraft
|
||||
let project: Project
|
||||
@Environment(Store.self) private var store
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
@@ -214,7 +208,8 @@ private struct ScheduleEditor: View {
|
||||
}
|
||||
HStack(alignment: .firstTextBaseline, spacing: 8) {
|
||||
Text("Run workflow").foregroundStyle(Palette.muted)
|
||||
let names = draft.workflow.isEmpty || draft.workflows.contains(draft.workflow) ? draft.workflows : [draft.workflow] + draft.workflows
|
||||
let saved = store.workflows.map(\.name)
|
||||
let names = draft.workflow.isEmpty || saved.contains(draft.workflow) ? saved : [draft.workflow] + saved
|
||||
ChoiceMenu(value: $draft.workflow, options: names.map { ($0, $0) })
|
||||
}
|
||||
Text("The workflow's brief, given to every stage (optional).")
|
||||
|
||||
@@ -289,15 +289,13 @@ private struct PrefsBox: View {
|
||||
private struct SavedCommands: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var list: [SavedCommand] = []
|
||||
@ViewState private var editing: String?
|
||||
@ViewState private var name = ""
|
||||
@ViewState private var command = ""
|
||||
@ViewState private var off: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(list, id: \.id) { c in
|
||||
ForEach(store.savedCommands, id: \.id) { c in
|
||||
if editing == c.id {
|
||||
form(c.id)
|
||||
} else {
|
||||
@@ -333,18 +331,6 @@ private struct SavedCommands: View {
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.onAppear {
|
||||
off = store.events.add { ev in
|
||||
if ev.kind == "command.saved" || ev.kind == "command.deleted" {
|
||||
load()
|
||||
}
|
||||
}
|
||||
load()
|
||||
}
|
||||
.onDisappear {
|
||||
off?()
|
||||
off = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func form(_ id: String?) -> some View {
|
||||
@@ -379,16 +365,6 @@ private struct SavedCommands: View {
|
||||
.padding(.vertical, 6)
|
||||
}
|
||||
|
||||
private func load() {
|
||||
Task {
|
||||
do {
|
||||
list = try await store.api.client.listSavedCommands().ok.body.json
|
||||
} catch {
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func save(_ id: String?, _ valid: Bool) {
|
||||
if !valid {
|
||||
return
|
||||
|
||||
@@ -248,7 +248,10 @@ final class Connection {
|
||||
async let lead: Void = store.loadLeadSessions()
|
||||
async let reads: Void = store.loadSummaryReads()
|
||||
async let terminals: Void = store.loadTerminals()
|
||||
_ = try await (me, projects, inbox, lead, reads, terminals)
|
||||
async let workflows: Void = store.loadWorkflows()
|
||||
async let notes: Void = store.loadLeadNotes()
|
||||
async let commands: Void = store.loadSavedCommands()
|
||||
_ = try await (me, projects, inbox, lead, reads, terminals, workflows, notes, commands)
|
||||
try await store.loadFocus()
|
||||
guard !stopped else {
|
||||
await store.stop()
|
||||
|
||||
@@ -66,6 +66,9 @@ final class Store {
|
||||
var projects: [Project] = []
|
||||
var tasks: [String: [ByreTask]] = [:]
|
||||
var schedules: [String: [Schedule]] = [:]
|
||||
var workflows: [WorkflowSummary] = []
|
||||
var leadNotes: [LeadNote] = []
|
||||
var savedCommands: [SavedCommand] = []
|
||||
var sessions: [String: [AgentSession]] = [:]
|
||||
var inbox: [InboxItem] = []
|
||||
private(set) var terminals: [String: [TaskTerminal]] = [:]
|
||||
@@ -105,7 +108,7 @@ final class Store {
|
||||
@ObservationIgnored private var sessionReloads: Set<String> = []
|
||||
@ObservationIgnored private var sessionRefetches: Set<String> = []
|
||||
@ObservationIgnored private var taskRefetches: [String: Set<String>] = [:]
|
||||
@ObservationIgnored private var terminalChanges = 0
|
||||
@ObservationIgnored private var changeCounts: [String: Int] = [:]
|
||||
@ObservationIgnored private var streamOpens = 0
|
||||
@ObservationIgnored private var globalStream: EventStream?
|
||||
@ObservationIgnored private var leadStream: EventStream?
|
||||
@@ -341,27 +344,58 @@ final class Store {
|
||||
}
|
||||
|
||||
func loadSchedules(_ projectId: String) async throws {
|
||||
let latest = claimRequest("schedules:\(projectId)")
|
||||
let list = try await api.client.listSchedules(path: .init(projectId: projectId)).ok.body.json
|
||||
if !latest() {
|
||||
try await fetch("schedules:\(projectId)", { try await api.client.listSchedules(path: .init(projectId: projectId)).ok.body.json }) { schedules[projectId] = $0 }
|
||||
}
|
||||
|
||||
func loadWorkflows() async throws {
|
||||
try await fetch("workflows", { try await api.client.listWorkflows().ok.body.json }) { workflows = $0 }
|
||||
}
|
||||
|
||||
func loadLeadNotes() async throws {
|
||||
try await fetch("leadNotes", { try await api.client.listLeadNotes().ok.body.json }) { leadNotes = $0 }
|
||||
}
|
||||
|
||||
func loadSavedCommands() async throws {
|
||||
try await fetch("savedCommands", { try await api.client.listSavedCommands().ok.body.json }) { savedCommands = $0 }
|
||||
}
|
||||
|
||||
// A reload lands only when no newer request for the same key has started since, and reloads instead when a stream event changed the key's data while it ran.
|
||||
private func fetch<T>(_ key: String, _ get: () async throws -> T, _ set: (T) -> Void) async throws {
|
||||
let latest = claimRequest(key)
|
||||
let seen = changeCounts[key, default: 0]
|
||||
let value = try await get()
|
||||
guard latest() else {
|
||||
return
|
||||
}
|
||||
schedules[projectId] = list
|
||||
if changeCounts[key, default: 0] != seen {
|
||||
try await fetch(key, get, set)
|
||||
return
|
||||
}
|
||||
set(value)
|
||||
}
|
||||
|
||||
private func changed(_ key: String) {
|
||||
changeCounts[key, default: 0] += 1
|
||||
}
|
||||
|
||||
private func applySchedule(_ ev: ByreEvent) {
|
||||
guard let projectId = ev.projectId else {
|
||||
return
|
||||
}
|
||||
changed("schedules:\(projectId)")
|
||||
var list = schedules[projectId] ?? []
|
||||
if ev.kind == "schedule.saved" {
|
||||
let sc = try! decodePayload(ev.payload, as: Schedule.self)
|
||||
list.removeAll { $0.id == sc.id }
|
||||
list.append(sc)
|
||||
upsert(&list, try! decodePayload(ev.payload, as: Schedule.self), by: \.id) { ($0.nextRun, $0.id) < ($1.nextRun, $1.id) }
|
||||
} else if let id = str(ev.payload.value, "id") {
|
||||
list.removeAll { $0.id == id }
|
||||
}
|
||||
schedules[projectId] = list.sorted { ($0.nextRun, $0.id) < ($1.nextRun, $1.id) }
|
||||
schedules[projectId] = list
|
||||
}
|
||||
|
||||
private func upsert<T, K: Equatable>(_ list: inout [T], _ item: T, by key: (T) -> K, order: (T, T) -> Bool) {
|
||||
list.removeAll { key($0) == key(item) }
|
||||
list.append(item)
|
||||
list.sort(by: order)
|
||||
}
|
||||
|
||||
private func claimRequest(_ key: String) -> () -> Bool {
|
||||
@@ -462,45 +496,33 @@ final class Store {
|
||||
}
|
||||
|
||||
func loadSessions(_ taskId: String) async throws {
|
||||
let latest = claimRequest("sessions:\(taskId)")
|
||||
let list = try await api.client.listSessions(path: .init(taskId: taskId)).ok.body.json
|
||||
if !latest() {
|
||||
return
|
||||
}
|
||||
sessions[taskId] = list
|
||||
try await fetch("sessions:\(taskId)", { try await api.client.listSessions(path: .init(taskId: taskId)).ok.body.json }) { sessions[taskId] = $0 }
|
||||
}
|
||||
|
||||
func messageTask(_ taskId: String, _ text: String, images ids: [String]? = nil) async throws {
|
||||
let sessions = try await api.client.listSessions(path: .init(taskId: taskId)).ok.body.json
|
||||
if let live = sessions.last(where: { $0.state != .completed }) {
|
||||
_ = try await api.client.sendMessage(path: .init(sessionId: live.id), body: .json(.init(text: text, images: ids))).accepted
|
||||
} else {
|
||||
_ = try await api.client.createSession(path: .init(taskId: taskId), body: .json(.init(prompt: text, images: ids))).created
|
||||
func messageTask(_ taskId: String, _ text: String, images: [String]? = nil, model: String? = nil, effort: String? = nil, firstModel: String? = nil, firstEffort: String? = nil, sendID: String? = nil) async throws {
|
||||
if !team(taskId).isEmpty {
|
||||
if images != nil {
|
||||
throw ApiError(status: 0, message: "The board takes text only; drop the images to post.")
|
||||
}
|
||||
_ = try await api.client.postToBoard(path: .init(taskId: taskId), body: .json(.init(text: text))).noContent
|
||||
return
|
||||
}
|
||||
if let live = currentSession(taskId), live.state != .completed {
|
||||
_ = try await api.client.sendMessage(path: .init(sessionId: live.id), body: .json(.init(text: text, images: images, model: model, effort: effort, sendId: sendID))).accepted
|
||||
return
|
||||
}
|
||||
_ = try await api.client.createSession(path: .init(taskId: taskId), body: .json(.init(prompt: text, model: firstModel, effort: firstEffort, images: images, sendId: sendID))).created
|
||||
}
|
||||
|
||||
func loadInbox() async throws {
|
||||
let latest = claimRequest("inbox")
|
||||
let items = try await api.client.listInbox().ok.body.json
|
||||
if !latest() {
|
||||
return
|
||||
try await fetch("inbox", { try await api.client.listInbox().ok.body.json }) {
|
||||
inbox = $0
|
||||
drafts?.prune(self)
|
||||
}
|
||||
inbox = items
|
||||
drafts?.prune(self)
|
||||
}
|
||||
|
||||
func loadTerminals() async throws {
|
||||
let latest = claimRequest("terminals")
|
||||
let changes = terminalChanges
|
||||
let list = try await api.client.listTerminals().ok.body.json
|
||||
if !latest() {
|
||||
return
|
||||
}
|
||||
if changes != terminalChanges {
|
||||
try await loadTerminals()
|
||||
return
|
||||
}
|
||||
terminals = Dictionary(grouping: list.terminals, by: \.taskId)
|
||||
try await fetch("terminals", { try await api.client.listTerminals().ok.body.json }) { terminals = Dictionary(grouping: $0.terminals, by: \.taskId) }
|
||||
}
|
||||
|
||||
func loadLeadSessions() async throws {
|
||||
@@ -508,12 +530,7 @@ final class Store {
|
||||
}
|
||||
|
||||
func loadFocus() async throws {
|
||||
let latest = claimRequest("focus")
|
||||
let f = try await api.client.getFocus().ok.body.json
|
||||
if !latest() {
|
||||
return
|
||||
}
|
||||
applyFocus(f)
|
||||
try await fetch("focus", { try await api.client.getFocus().ok.body.json }) { applyFocus($0) }
|
||||
}
|
||||
|
||||
func applyFocus(_ f: Focus) {
|
||||
@@ -802,6 +819,15 @@ final class Store {
|
||||
spawn("load summary reads") {
|
||||
try await self.loadSummaryReads()
|
||||
}
|
||||
spawn("load workflows") {
|
||||
try await self.loadWorkflows()
|
||||
}
|
||||
spawn("load lead notes") {
|
||||
try await self.loadLeadNotes()
|
||||
}
|
||||
spawn("load saved commands") {
|
||||
try await self.loadSavedCommands()
|
||||
}
|
||||
for (_, task) in activeTasks() {
|
||||
spawn("load sessions") {
|
||||
try await self.loadSessions(task.id)
|
||||
@@ -829,7 +855,7 @@ final class Store {
|
||||
confirmSend(taskId, ev)
|
||||
}
|
||||
if ev.kind == "terminals.changed", let taskId = ev.taskId {
|
||||
terminalChanges += 1
|
||||
changed("terminals")
|
||||
terminals[taskId] = try! decodePayload(ev.payload, as: TerminalList.self).terminals
|
||||
}
|
||||
if ev.seq == 0 {
|
||||
@@ -850,6 +876,30 @@ final class Store {
|
||||
}
|
||||
case "schedule.saved", "schedule.deleted":
|
||||
applySchedule(ev)
|
||||
case "workflow.updated":
|
||||
changed("workflows")
|
||||
upsert(&workflows, try! decodePayload(ev.payload, as: WorkflowSummary.self), by: \.name) { $0.name < $1.name }
|
||||
case "workflow.deleted":
|
||||
changed("workflows")
|
||||
if let name = str(ev.payload.value, "name") {
|
||||
workflows.removeAll { $0.name == name }
|
||||
}
|
||||
case "lead.note_saved":
|
||||
changed("leadNotes")
|
||||
upsert(&leadNotes, try! decodePayload(ev.payload, as: LeadNote.self), by: \.id) { ($0.createdAt, $0.id) < ($1.createdAt, $1.id) }
|
||||
case "lead.note_deleted":
|
||||
changed("leadNotes")
|
||||
if let id = str(ev.payload.value, "id") {
|
||||
leadNotes.removeAll { $0.id == id }
|
||||
}
|
||||
case "command.saved":
|
||||
changed("savedCommands")
|
||||
upsert(&savedCommands, try! decodePayload(ev.payload, as: SavedCommand.self), by: \.id) { ($0.name, $0.id) < ($1.name, $1.id) }
|
||||
case "command.deleted":
|
||||
changed("savedCommands")
|
||||
if let id = str(ev.payload.value, "id") {
|
||||
savedCommands.removeAll { $0.id == id }
|
||||
}
|
||||
case "task.created", "task.title", "task.merge_title", "task.merge_queued", "task.merge_unqueued", "task.merge_after_review", "task.merge_after_review_ended", "task.merge_after_review_canceled", "task.parent_merge_started", "task.parent_merge_stopped", "task.merge_started", "task.merge_ended", "task.discard_started", "task.merged", "task.discarded", "task.state", "task.ready", "task.discard_suggested", "task.ready_cleared", "snapshot.created":
|
||||
if let taskId = ev.taskId, ["task.merged", "task.discarded"].contains(ev.kind) || (ev.kind == "snapshot.created" && (try? decodePayload(ev.payload, as: Snapshot.self))?.isCutPoint != true) {
|
||||
clearTask(self, taskId)
|
||||
|
||||
@@ -178,23 +178,15 @@ final class SwitcherModel {
|
||||
#if os(macOS)
|
||||
@ObservationIgnored var pending: NSEvent?
|
||||
#endif
|
||||
var workflows: [WorkflowSummary] = []
|
||||
|
||||
init(store: Store, overlays: Overlays, anchor: CGRect) {
|
||||
self.store = store
|
||||
self.overlays = overlays
|
||||
self.anchor = anchor
|
||||
Task {
|
||||
do {
|
||||
workflows = try await store.api.client.listWorkflows().ok.body.json
|
||||
} catch {
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func workflowMatch(_ title: String) -> (WorkflowSummary, String)? {
|
||||
for w in workflows.sorted(by: { $0.name.count > $1.name.count }) {
|
||||
for w in store.workflows.sorted(by: { $0.name.count > $1.name.count }) {
|
||||
guard title.lowercased().hasPrefix(w.name.lowercased()) else {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ func terminalFocused() -> Bool {
|
||||
struct TerminalTab: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(Overlays.self) private var overlays
|
||||
@ViewState private var commands: [SavedCommand] = []
|
||||
@ViewState private var selected: String?
|
||||
@ViewState private var status = ""
|
||||
@ViewState private var failure: String?
|
||||
@ViewState private var starting = false
|
||||
@ViewState private var off: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
let taskId = store.selectedTaskId
|
||||
@@ -55,7 +53,7 @@ struct TerminalTab: View {
|
||||
.buttonStyle(SmallLinkButtonStyle())
|
||||
.help("Open another shell in the task's worktree")
|
||||
.padding(.leading, 8)
|
||||
ForEach(commands, id: \.id) { c in
|
||||
ForEach(store.savedCommands, id: \.id) { c in
|
||||
Button("▸ \(c.name)") {
|
||||
start(c.id)
|
||||
}
|
||||
@@ -105,28 +103,6 @@ struct TerminalTab: View {
|
||||
start(nil)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
off = store.events.add { ev in
|
||||
if ev.kind == "command.saved" || ev.kind == "command.deleted" {
|
||||
loadCommands()
|
||||
}
|
||||
}
|
||||
loadCommands()
|
||||
}
|
||||
.onDisappear {
|
||||
off?()
|
||||
off = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func loadCommands() {
|
||||
Task {
|
||||
do {
|
||||
commands = try await store.api.client.listSavedCommands().ok.body.json
|
||||
} catch {
|
||||
overlays.showAlert(errorText(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func start(_ commandId: String?) {
|
||||
|
||||
@@ -50,59 +50,18 @@ struct WorkflowsView: View {
|
||||
private struct WorkflowList: View {
|
||||
let edit: (String) -> Void
|
||||
@Environment(Store.self) private var store
|
||||
@ViewState private var list: [WorkflowSummary]?
|
||||
@ViewState private var failure: String?
|
||||
@ViewState private var loads = 0
|
||||
@ViewState private var off: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if let list {
|
||||
if list.isEmpty {
|
||||
hint("No saved workflows. Click New to write one.")
|
||||
} else {
|
||||
ForEach(list.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }, id: \.name) { w in
|
||||
WorkflowRow(workflow: w) { edit(w.name) }
|
||||
}
|
||||
if store.workflows.isEmpty {
|
||||
Text("No saved workflows. Click New to write one.").foregroundStyle(Palette.muted)
|
||||
} else {
|
||||
ForEach(store.workflows.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }, id: \.name) { w in
|
||||
WorkflowRow(workflow: w) { edit(w.name) }
|
||||
}
|
||||
} else if let failure {
|
||||
hint(failure)
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.onAppear {
|
||||
off = store.events.add { ev in
|
||||
if ev.kind == "workflow.updated" || ev.kind == "workflow.deleted" {
|
||||
load()
|
||||
}
|
||||
}
|
||||
load()
|
||||
}
|
||||
.onDisappear {
|
||||
off?()
|
||||
off = nil
|
||||
loads += 1
|
||||
}
|
||||
}
|
||||
|
||||
private func hint(_ text: String) -> some View {
|
||||
Text(text).foregroundStyle(Palette.muted)
|
||||
}
|
||||
|
||||
private func load() {
|
||||
loads += 1
|
||||
let n = loads
|
||||
Task {
|
||||
do {
|
||||
let loaded = try await store.api.client.listWorkflows().ok.body.json
|
||||
if n == loads {
|
||||
list = loaded
|
||||
}
|
||||
} catch {
|
||||
failure = errorText(error)
|
||||
list = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user