Carry the source transcript into forks and cross-link fork points

This commit is contained in:
Ian Gulliver
2026-10-20 09:14:44 -07:00
parent 29c993cde0
commit 09d6c6e023
10 changed files with 208 additions and 123 deletions
+50 -49
View File
@@ -4368,55 +4368,56 @@ var swaggerSpec = []string{
"nXQZlLaLHCYBCk/RqkYV2YhOaDc0eDyPlQO3+WyXmAMF/XGsUXHySx1SoU3IvRjBoupT+NxJ3ZRzM0Fm",
"N/ucQfhZHGukC3quN9KEY6363SZ/+dwuZKHZi4qt0QutakxXA1sPm4in+VqFO6UMVkzzxCd2lv5l72qa",
"jl1dL2H04+J3qBzlvSwkpX64cv5H98Mb8tH92KaecV97hYbHC4HTjcJtGHNRkWG+Yjz+gxLtWrvRIwIp",
"97tkiXZRYqar1EIrH2PyAoZUbTkl4jcm4Ev4q0YuWkinRKMWQdg2XIjEV9dCcVtoECuvUgpv6W6AsX0O",
"m8b1ZBslb1kn1E0nrJxtlysxnVK+Mr9dN9rcVKk6aTkjTTq07hCvedlIvzr1ChoFEhEaSYZv25sLenTE",
"spfWcWRZ4nxi7e09J/D3OTUvtQOvsj2pPxNSnPi4Y3/0U4QFcD6/ZoWysGXnkrUgcHJVDfvPQSWAnHdO",
"B5UaR6/gKur/YqIGEtAooWZPkYuBsdqwFrgXqahjLJoB4djUwqbSStILb62Jep4YdplZu2K1H2YJ4YWs",
"SUAXayUNeplciNrZDUVxYggeMmQXMUJiaEODUDhMN6MDj0LV4H1RIwCdsHgeE9xA9zDPSHy6D0Uqy0QC",
"OXdG6pQqghU/5GyeTq2h6MTAbSb1p30crUtCd61g8DQSs7U++tj49ho+9ReYGoO6ucOQZ6wT1W7EAthM",
"pE5EUcMqvRvJmAMjlVWo//7ZVKgVB+fSMiteE4Phd7GqDU76EbHrg5yvqEiUwqtfh2FzFDPeDxdvJPvl",
"Yyy55O/LIXMAzPvEiX/CrKux8HAhTPyrZ48eJ36UcIOQOUasoeBxXFHctCx6/NnTL1/QAZllT/EBp5Zt",
"Ix2JNmWd6FRcgZobeSQuyklVo6Tw+jcOQLwQlD0aO4otgXDzxj06dwb8/Kdlzw4wYPe6oR9VjHnBHY3Q",
"TtiRPzYLhgVJxg9BqjGkGxbji8IMMOevpbup7R1/QVG49LEMQc5Xa0r5jZhOCSNs0wYFCD2Gaxd8DTjl",
"bXMLtJ4RH5W7sTyRNl7XqvfhdFCzqMKKRTily//3u0rM4X5EWUu9C8qgf9f4+Xm1JnP3MYmZP/0B+Gct",
"cuNvl//2bt387jVyAHVjjZwHnfU//TEpAwoKn180S8HATGRwGjOsTpnywliz0A60IviYFSdZugT06mOf",
"YBSLuJtTbaokqWisGXOrA5536BtsMey8hx0XvQyFATRsmpjfz//aSr8aEZC0CRalymhng24zS2FhjdYc",
"MHNGrfhAqR5LFhXV5kigSeHeU0d1aulKbFpchuacLZB3uxILKGKwsO5OxtRh3cSwCCdAMtgYLW964mJJ",
"TizIhbj6Ork7cVQEvtI9YPQkvmSXTMllotHyCYGT3KBmmAxceBWe5nEbXHW460Fj3c+lU94PR41WW4bZ",
"QLovpYuhnYQ4Hv0ultZbKUzvHHTTrXzeWMwxP4gL2fXwYt1Xmm0Ud2HdqYoGJp8hO2HFnrYs72K2AgIZ",
"P+lC8himqiZkhVNBfQxC9xi6VGSLWiS9CczI31AOeZ5jEE9oA4RecPrrmHXpaZVAGudHgEjntI5Zs/NZ",
"atMf+vtvnr+MQWDK1NEQYRf9ZpzXivFmBouc8QIJL7pNMU9LuoYOYoNtEViHJt8OHmQIyan4RjNO4x51",
"TPtQpVFQVUQXAntneKeRJ5rFeAg4LjAmI/ZWhfNh4ZzkscInI++gdzjWslYVaZy0j4QlxWDiQ1pC1Xkt",
"cjd9usK535IZmxO19CpUc9BJ3zavPWsysgxylDF9CP2LTFdGuwCKrk2krjI3exobsuirJ18/+4+nojWN",
"8v1cE13n5VRfbvmx0aHlHAcY7VeVStvzJu3ekjgZOiW5drC/GffYhczhw9i76aTacReoJtktNVJyH6/C",
"zmcBm57jqWw0pmXhgqH/x7+L+Uo6OQ/KIfkjroF0/HRG1lxutlD82hVFFfiAnCf4wnx6HidkTbA5Rd+5",
"VKd8UM8j9UofFCjhBVLTPoGdZsXq11QGgj49sug84xas7DV9nj15wz19KGe6+Hzesfu0kAS4TNNY4eWE",
"V5dXyCPgiyy35GcJP/pE+ktiH3Hup/mZjEnQdr1+aifvvJCCTurdkHDHMiVM7nZrm0Gji0Td0y1dKEIu",
"zVw1OMxzGIBjvn4nh9547QAm3DEYzJevCoziweeXd1LAVM8SlRipfWSro1OZo2SVaUvA6APCUqPngdOi",
"qaUyiD1mKfinqkkzCWUWTRBZlye+F8ZFhzmGNIwZsg6ys6OX66vfPYKFYV4ORfrCSdSGw4/3Glk21oUC",
"uql3G+sj60R/zKBplbkjSGLDPZXiBNlS6sbPZcM1d3shdfCxMC2mxrQLlJTBOdT6UFEp3++vrt5cVkOB",
"qBNGrIvmq7Jqm7Ip8Yo/TzYljqU9No0Lze3LJ30opkF8+qYNO8k9BwuTN8qziS2GGUWfwycU9BkTrHhy",
"D+ZGaB96mmeHzgqBaa671YUw4KetyT/uqEpieuPIK8wMrUwsZAb4VUKZeCvn8UvHeP7HcT7uSvtCtH3V",
"+3JwP27+3p4OGz3HMAyF43141RXOSahxgtSX4vzx+9nCuhnRZ7iF2uVS+TBjGlEJb3tUpKujQx8H233L",
"38y4DwW1c/DDKbUFc7+LeJewspRGgOf9Ftf3OzBcEW71l09ljrmckkcINq1QcNwolwVk9FRtxTvh7We8",
"Et7e60b4p7kQCtmOd+H8xecc7vE3IxmYv5ytOBRAbPoLGQvR6gL9dIiiw3oT+kkMypG9FCL3uMkIuPff",
"JwfaMfx/5qD3z8H0R9J4FGFNjYc2i8jby2inYgVih1ce1PiqCxG8U06lIMAbBSiGJVL9GbD6pVmevTcf",
"jmP3L9jP9y56ACadkQxjosBlgsPnoEdxtGNpUQL8PwHxj1OF/TqDCriPphIps6Lm/jmmDiR7S2WMySuy",
"lIqSVJPJvyULLKQDUaWy8VTDtUeLUZMHCKZNq/ywA2o7t+ZWOU+mZopN9MHaeqdsM4pRt8rU1nGkahwG",
"9uJpV5i5ywfRUfVYwL8XhUXFTNAgtc+VFk1wdRULWWRVkZO5oIt8xjXF/mUQXeWfcmrNb7NKypP/lTzu",
"GMKepeB+VKXkUQnaymmy8xjDLl+2Vp6dIL36FXwbsXgT40iTFcuPVtOEVhyiHpXeIJZhym0qkqqwlgPD",
"BUW1Cs5H5whBec93NIvS3WRpyh+Ls8lG+Owp9x+akG2AckG5tTay2eMS+4u6vrTzGxXYp68rGx/xgPuo",
"hBSNXWoj/Eo1DRVUyD0Gs+CJuW3APYtufL2g6tiZuhhJHqoYY3wpsbh3ETvjoIyZc2VCsxUbpw3aoUKM",
"y7Bt2LTMDANhx7L54DXvo4cukzRPY84brUwcDVUkhaasugJ3X3RgwOoS6WWswivFFU/yUv/WORYpBo96",
"p6PrEq2THSKQ8s5trcTXz549S24T0BwgGFqf4kOU9HudGOP4x/kxwp7sJSV7HdVHvN9hgx/e6dBT8qtn",
"X+2i6CWWNOzQskPYjbPBzm3zx3b3bQ0x3IerZw1FS8BkvhHQs2XMJlVb5c1JKMb3/hxHf8wKGXGMsSoZ",
"/D7eNtN/sR2Omc73CoSxUccBLpz9LYWwoap7jgx0XWWpNICSTsUPMignVK3ZpkhaXZ8Ljdh3bc1JYDCP",
"8IVMm36Jc35kHWoaZ8xVGIBg0gq+fGktzvTMtWa/3S/6Ng43ybUmsyArUJp7Eex5zFOjDWUN7iNKJyag",
"UykqAij0McigYgyf28ZCKxIZBrXeBCzpXnUesPzYs3BGV3SWHCLyCSCbjWBQ3NW37aPmis2HGVPxtubE",
"J1h/gegTt32/bveX1OpzKFPiaMxCH6NTwVxVCYdPKEaeaVFq19muc543QeDsPbw7opbhOHHaU8tQALnh",
"65JIqw5YQJLzpMztZvsg/BhL4vcwErqVwwCNo0IqMsiD58g5Hv04g5cd0ZBe/I/nr3/AK0SaLA6IkxrE",
"bHGPdFLwv08hD0ddEoYqbRo5zxzc86oiBA6QfcaBgdHg/3n5049RlwOoq33ykgQK/ZUIVnz17FnPSzLD",
"qCg5E2b1ZjLEsVLevbaPLcdIw0chyufLa75DM/aW6x7c5/eq2PThw/8cAA==",
"97tkiXZRYqar1EIrH2PyAoZUbTkl4jcm4Ev4q0YuGl2UG7UIwrbhQiS+uhaK20KDWHmVUnhLdwOM7XPY",
"NK4n2yh5yzqhbjph5Wy7XInplPKV+e260eamStVJyxlp0qF1h3jNy0b61alX0CiQiNBIMnzb3lzQoyOW",
"vbSOI8sS5xNrb+85gb/PqXmpHXiV7Un9mZDixMcd+6OfIiyA8/k1K5SFLTuXrAWBk6tq2H8OKgHkvHM6",
"qNQ4egVXUf8XEzWQgEYJNXuKXAyM1Ya1wL1IRR1j0QwIx6YWNpVWkl54a03U88Swy8zaFav9MEsIL2RN",
"ArpYK2nQy+RC1M5uKIoTQ/CQIbuIERJDGxqEwmG6GR14FKoG74saAeiExfOY4Aa6h3lG4tN9KFJZJhLI",
"uTNSp1QRrPghZ/N0ag1FJwZuM6k/7eNoXRK6awWDp5GYrfXRx8a31/CpvxAAWermDkOesU5UuxELYDOR",
"OhFFDav0biRjDoxUVqH++2dToVYcnEvLrHhNDIbfxao2OOlHxK7vSiukTJTgbz8MnaO48X7IeCPZNx/j",
"ySVnrIW7DxL59bseWnsJoUhhAK25IEYsAgRISfdqHCwpNOaoWoyaVrvovR10KYN4Al15C/hHxnB4JeyC",
"fz294BB8PgIQcxqD1Lhs5EzXYxHwgCb3iYD/hPlkY0nlQgD8V88ePQL+KLENIXOMwEZh8biiuA9ZXPyz",
"p1++CAfS2J6yCk4t20Y6EtrK2t6puAIFPnJ/XG6Uj4Lw+jcOrbwQlBcbO4othbGxAMWj850gqXxaxvMA",
"a3kv3uNRBbQX3NHIrQA78sdmLrHUyvghSNWT4AykUIQdMQ2I9Gvpbmp7x19QfDF9LEOQ89WakpkjplMq",
"DNu0QQFCj+HaBV9uTnnb3MKlwoiPautYeEkbr2vV+3A6qMZUYS0mnNLl//tdJeZwe6AUqd4FZdBzbfz8",
"vFqTIf+YlNOf/gD8s5bv8bfLf3u3bn736j+AurH6z4PO+p/+mJQBRaDPL3SmMGcmMjiNGdbdTBlvrFlo",
"B/oefMwqoSwRBPorsrczCnzczak2VZLBNFbDudUBzzv0DVYmdkvEjov+k8IAGjZNzFzof22lX42IftoE",
"i/JytCBCt3WfoR6s0ZoDBtyo7x+YC2IxpqJBAAk0mRJ6irZO4V6JTYvL0JyNBjKKV2IB5RkW1t3JmBSt",
"mxiWFwVIBhvzAJieIFySgAsSL66+To5cHO+Br3QPGD1ZNllcU9qcaI59QuAkB68ZpjkXXoWneUQK11Pu",
"etBY0XTplPfDUaM9mmE20FuUEuHQTmpQWb6LRQMx21Jrgm66lc8bi9nzBxEvu75rrNVLs42CPKw71QfB",
"tDpkAa1YbGJJHvMwEMj4SRdsyDBVNSErnArqYxCUyNCl8mHUImmEYEb+hrLj8xyDeEIbIPSCE3vHfFJP",
"qwTSOD8CRDqndcwHns9Sm/7Q33/z/GUMb1OmjiYWu+g344xdjDczWOSMF0h40W2KeVrSonQQG2yLwAo7",
"+XbwIENITsU3mnEa96hj2ofKmoISJjpH2DvDO4080SxGesBxgTEZsbcqnA9LAiVfHD4ZeQe9w7GWtapI",
"l6Z9JCwpuhQf0hKqzh+Tu+nTFc5qlwR8TkHTq73N4TR9rwPtWUeT5cajXPBD6F9kWkDaBVDhbSJ1lbke",
"w9iQxZU9+frZfzwVrWngrOdZNLrOy0nM3PJj417L2RswjrEqFe3nTdq9JXEydEpyvWd/M+6xC5kri7F3",
"00m14whRTbJbqqyboquw88bApud4KhuNCWe4FOr/8e9ivpJOzoNySP6IayDrBZ2RNRfSLZT1dkVRBT4g",
"txC+MJ+exwlZE2xO0Xcu1Skf1PNIvdIHBUp4gdS0T2CnWRn+NRW4oE+PLKfPuAUre02fZ0/ecE8fyjk8",
"Pp/f7z79KgEu06FWeDnh1eUV8gj4Isua+VkCqz6RZpbYR5z7aX4mY3q3XX+m2sk7L6Sgk3o3JNyxAAuT",
"u92qbdDoIlH3dEsXyqtLM1cNDvMcBuBott/JVTleO4AJdwwG8+WrAqN48PnlnRQK1rOxJUZqH9nq6FTm",
"Alpl2hIwZ4Gw1Oh54IRvaqkMYo9ZCv6patJMQgFJE0TW5YnvBajRYY7BGmMmuoPs7Ojl+up3j81hmJeD",
"rL5wErXhwOq95qONdaGAburdxvrIOtEfM2haZY4WkthwT0VGQbaUuvFz2XA14V6wIHwsTItJP+0CJWVw",
"e7U+VFSk+PurqzeX1VAg6oQR66JhTo0act7EFX+ePFEcJXxsghqa25dP+lBMg8j7TRt20pYOFiZvlGfD",
"YQygit6UTyicNaaO8eT4zI3QPvQ0z3udlTjTXFGsC87AT1uTf9xRlcT0xpFXmPNamViiDfCrhDLxVs4j",
"s46JaYjjfNyV9oVo+6r35bQFuPl7ezps9BzDMBSO9+FVVxIoocYJUl/KYIDfzxbWzYg+Wyd8u1wqH2ZM",
"IyrhbY+KdBWC6ONgu2/5mxn3oaAqEH44pbbgyOCSrTpiZSlBAs/7La7vd2C4ItzqL5/KHHM5JV8XbFqh",
"4LhRLgs16anainfC2894Jby9143wT3MhFPI478L5i8+m3ONvRnJLfzlbcSg02vQXMhZ81oUw6hBFh/Um",
"9NMzlGOWKfjvcdMscO+/T3a3Y/j/zPXwn4Ppj6TxKMKaGg9tFpG3l9FOxQrEDq88qPFVF/x4p5xK4Y03",
"ClAMi7/6M2D1S7M8e28+HMfuX2ADZ++ib2PSGckwJgpcJjh8DnoURzuWFiXA/xMQ/zhV2K8zcLZ7NJVI",
"mRU198+edSCNXSrQTP6epSSbpJpM/i1ZyCQdiCoVxKfqtD1ajJo8QDBtWuWHHVDbuTW3ynkyNVPUpQ/W",
"1jsFqVGMulWmto5jcOMw6PjYlZzuMl10VJ0icAcJ9ahMCxqk9jkJowmurmKJjqzeczIXdDHduKbYvwyi",
"q2lUThr6bVYjevK/0uIdQ9iz5OKPqpQ8KvVcOQF4Hj3ZZQLXyrMTpFe/gm8jlqViHGk4+tgalaymCa04",
"+D4qvUEsw2TiVP5VYZUKhguKahWcj84RgjK672gWpbvJErA/FmeTjfDZiwk8NNXcAOWCcmttZLPHJfYX",
"dX1p5zcqsE9fVxA/4gH3UQkpGrvURviVahoqFZF7DGZhIXPbgHsW3fh6QXW/M3UxkjxUMcbIWWJx7yJ2",
"xkEZM+fKhGYrNg5uiZr9UmB024ZNy8wwEHblISSigQfsocskzdOY80YrE0dDFUmhKauuwN0XHRiwbkZ6",
"GesLS3HFk7zUv3WORYrBo97p6LpE62SHCKS8c1sr8fWzZ8+S2wQ0BwiG1qfIFyX9XifGOP5xfoywJ3tJ",
"yV5H9RHvd9jgh3c69JT86tlXuyh6icUaO7TsEHbjbLBz2/yx3X1bQwz34bpgQ9ESMJlvBPRsGbNJ1VZ5",
"cxKKkcs/x9Efs/ZHHGOs/ge/j7fN9F9sh2MO970CYWzUcYALZ39LwXmo6uaQnSpLEgKUdCp+kEE5oWrN",
"NkXS6vpcaMS+a2tOAoN5hC9k2vRLnPMj61DTOGOuwgAEk1bw5UtrcaZnrjX77X7Rt3G4Sa41mQVZgdLc",
"i2DPYwYebSgfch9ROjEBnUpREUBBnUEGFaMT3ZY+QaIhQ1DrTcBi9VXnAcuPPQtndEVnaS8inwCy2QgG",
"xV192z5qFtx8mDEVb2tOfIL1F4g+cdv363Z/Sa0+hzIljsYs9DE6FczClXD4hKL/mRaldp3tOud5EwTO",
"3sO7I6o0jhOnPVUaBZAbvi6JtOqQhynO7Wb7IPwYS0/4MBK6lcMAjaNCKjLIg+fIOR79OIOXHdGQXvyP",
"569/wCtEmiwOiNM1xDx4j3RS8L9PIQ9HXRKGKm0aOc8c3PN6KQQOkH3GgYFx7v95+dOPUZcDqKt98pIE",
"Cv2VCFZ89exZz0syw6goORNm9WYyxLFSRsG2jy3HSMNHIcrny9i+QzP2FiIf3Of3qkX14cP/HAA=",
}
// decodeSpec returns the embedded OpenAPI spec as raw JSON bytes,
+1 -1
View File
@@ -733,7 +733,7 @@ func (s *Server) ListTaskEvents(w http.ResponseWriter, r *http.Request, taskID s
limit = *params.Limit
}
events, err := s.store.TaskEvents(r.Context(), taskID, after, limit)
events, err := s.store.TranscriptEvents(r.Context(), taskID, after, limit)
if err != nil {
writeStoreError(w, err)
return
+1 -1
View File
@@ -35,7 +35,7 @@ func (s *Server) StreamEvents(w http.ResponseWriter, r *http.Request, params api
case lead:
return s.store.LeadEvents(r.Context(), viewer, after, backfillPage)
case taskID != "":
return s.store.TaskEvents(r.Context(), taskID, after, backfillPage)
return s.store.TranscriptEvents(r.Context(), taskID, after, backfillPage)
}
return s.store.EventsAfter(r.Context(), viewer, after, backfillPage)
}
+40 -1
View File
@@ -360,7 +360,11 @@ func (s *Store) CreateTask(ctx context.Context, t api.Task, frozen *FrozenWorkfl
if err != nil {
return err
}
return emit(api.Event{Kind: "task.created", ProjectId: &t.ProjectId, TaskId: &t.Id, Actor: actor, Payload: ToPayload(t)})
err = emit(api.Event{Kind: "task.created", ProjectId: &t.ProjectId, TaskId: &t.Id, Actor: actor, Payload: ToPayload(t)})
if err != nil || forkedFrom == "" {
return err
}
return emit(api.Event{Kind: "task.forked", ProjectId: &t.ProjectId, TaskId: &forkedFrom, Actor: actor, Payload: map[string]any{"fork": t.Id, "forked_at": forkedAt}})
})
}
@@ -958,6 +962,41 @@ func (s *Store) TaskEvents(ctx context.Context, taskID string, after int64, limi
return scanEvents(rows)
}
func (s *Store) TranscriptEvents(ctx context.Context, taskID string, after int64, limit int) ([]api.Event, error) {
where := "task_id = ?"
args := []any{taskID}
for id := taskID; ; {
from := ""
at := 0
err := s.db.QueryRowContext(ctx, `SELECT forked_from, forked_at FROM tasks WHERE id = ?`, id).Scan(&from, &at)
if err != nil {
return nil, err
}
if from == "" {
break
}
cut := int64(0)
err = s.db.QueryRowContext(ctx, `
SELECT coalesce(max(seq), 0) FROM events
WHERE task_id = ? AND kind = 'snapshot.created' AND json_extract(payload, '$.n') = ?
`, from, at).Scan(&cut)
if err != nil {
return nil, err
}
where += " OR (task_id = ? AND seq <= ?)"
args = append(args, from, cut)
id = from
}
rows, err := s.db.QueryContext(ctx, `
SELECT `+eventColumns+`
FROM events WHERE (`+where+`) AND seq > ? ORDER BY seq LIMIT ?
`, append(args, after, limit)...)
if err != nil {
return nil, err
}
return scanEvents(rows)
}
func (s *Store) FirstPrompt(ctx context.Context, taskID string) (string, error) {
text := ""
err := s.db.QueryRowContext(ctx, `