@guuey/chat 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +134 -35
- package/dist/policy.d.ts +9 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +1 -1
- package/dist/react/components.d.ts +1 -1
- package/dist/react/components.d.ts.map +1 -1
- package/dist/react/components.js +3 -2
- package/dist/react/guuey-chat.d.ts +32 -2
- package/dist/react/guuey-chat.d.ts.map +1 -1
- package/dist/react/guuey-chat.js +167 -16
- package/dist/react/structural-identity.d.ts +3 -0
- package/dist/react/structural-identity.d.ts.map +1 -0
- package/dist/react/structural-identity.js +45 -0
- package/dist/react/use-transcript.d.ts.map +1 -1
- package/dist/react/use-transcript.js +5 -3
- package/dist/strings.d.ts +4 -0
- package/dist/strings.d.ts.map +1 -1
- package/dist/strings.js +2 -0
- package/dist/types.d.ts +62 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/corpus/__snapshots__/corpus.test.ts.snap +269 -0
- package/src/index.ts +1 -0
- package/src/plan.ts +133 -37
- package/src/policy.ts +10 -3
- package/src/react/components.tsx +7 -2
- package/src/react/guuey-chat.tsx +218 -15
- package/src/react/structural-identity.ts +47 -0
- package/src/react/use-transcript.ts +5 -2
- package/src/strings.ts +6 -0
- package/src/types.ts +61 -5
- package/styles.css +10 -0
|
@@ -833,6 +833,7 @@ exports[`corpus > 1. forty-tools — grouping caps the rows; plan stays O(groups
|
|
|
833
833
|
],
|
|
834
834
|
"recovery": null,
|
|
835
835
|
"status": null,
|
|
836
|
+
"views": [],
|
|
836
837
|
}
|
|
837
838
|
`;
|
|
838
839
|
|
|
@@ -969,6 +970,7 @@ exports[`corpus > 2. midstream-tool-failure — failed inline; badge without unr
|
|
|
969
970
|
],
|
|
970
971
|
"recovery": null,
|
|
971
972
|
"status": null,
|
|
973
|
+
"views": [],
|
|
972
974
|
}
|
|
973
975
|
`;
|
|
974
976
|
|
|
@@ -992,6 +994,7 @@ exports[`corpus > 3. cold-start — R12 escalation at 0 / 2.5 s / 15 s 1`] = `
|
|
|
992
994
|
"kind": "status",
|
|
993
995
|
"state": "starting",
|
|
994
996
|
},
|
|
997
|
+
"views": [],
|
|
995
998
|
}
|
|
996
999
|
`;
|
|
997
1000
|
|
|
@@ -1065,6 +1068,7 @@ exports[`corpus > 4. consent-gate — pending grant-mode card → answered one-l
|
|
|
1065
1068
|
],
|
|
1066
1069
|
"recovery": null,
|
|
1067
1070
|
"status": null,
|
|
1071
|
+
"views": [],
|
|
1068
1072
|
}
|
|
1069
1073
|
`;
|
|
1070
1074
|
|
|
@@ -1091,6 +1095,7 @@ exports[`corpus > 5. bypass-text-only — plan identical to a silver text-only s
|
|
|
1091
1095
|
],
|
|
1092
1096
|
"recovery": null,
|
|
1093
1097
|
"status": null,
|
|
1098
|
+
"views": [],
|
|
1094
1099
|
}
|
|
1095
1100
|
`;
|
|
1096
1101
|
|
|
@@ -1168,6 +1173,36 @@ exports[`corpus > 7. view-never-handshakes — channel-aware labels on both chan
|
|
|
1168
1173
|
],
|
|
1169
1174
|
"recovery": null,
|
|
1170
1175
|
"status": null,
|
|
1176
|
+
"views": [
|
|
1177
|
+
{
|
|
1178
|
+
"actionScope": null,
|
|
1179
|
+
"channel": "inline",
|
|
1180
|
+
"key": "view.tInline",
|
|
1181
|
+
"mount": {
|
|
1182
|
+
"channel": "inline",
|
|
1183
|
+
"resource": {
|
|
1184
|
+
"mimeType": "text/html",
|
|
1185
|
+
"text": "<p>form</p>",
|
|
1186
|
+
"uri": "ui://forms/1",
|
|
1187
|
+
},
|
|
1188
|
+
},
|
|
1189
|
+
"origin": "live",
|
|
1190
|
+
"phase": "no-handshake",
|
|
1191
|
+
"title": "render form",
|
|
1192
|
+
},
|
|
1193
|
+
{
|
|
1194
|
+
"actionScope": "ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c0ffee",
|
|
1195
|
+
"channel": "locator",
|
|
1196
|
+
"key": "view.tGgui",
|
|
1197
|
+
"mount": {
|
|
1198
|
+
"channel": "locator",
|
|
1199
|
+
"resourceUri": "ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c0ffee",
|
|
1200
|
+
},
|
|
1201
|
+
"origin": "live",
|
|
1202
|
+
"phase": "no-handshake",
|
|
1203
|
+
"title": "ggui render",
|
|
1204
|
+
},
|
|
1205
|
+
],
|
|
1171
1206
|
}
|
|
1172
1207
|
`;
|
|
1173
1208
|
|
|
@@ -1212,6 +1247,7 @@ exports[`corpus > 8. aborted-mid-stream — partial kept + Stopped.; no orphaned
|
|
|
1212
1247
|
"kind": "status",
|
|
1213
1248
|
"state": "aborted",
|
|
1214
1249
|
},
|
|
1250
|
+
"views": [],
|
|
1215
1251
|
}
|
|
1216
1252
|
`;
|
|
1217
1253
|
|
|
@@ -1267,6 +1303,29 @@ exports[`corpus > 9. history-dead-locators — the expired path beside a healthy
|
|
|
1267
1303
|
],
|
|
1268
1304
|
"recovery": null,
|
|
1269
1305
|
"status": null,
|
|
1306
|
+
"views": [
|
|
1307
|
+
{
|
|
1308
|
+
"actionScope": "ui://ggui/render/old/1",
|
|
1309
|
+
"channel": "locator",
|
|
1310
|
+
"key": "card.1",
|
|
1311
|
+
"mount": {
|
|
1312
|
+
"channel": "locator",
|
|
1313
|
+
"resourceUri": "ui://ggui/render/old/1",
|
|
1314
|
+
},
|
|
1315
|
+
"origin": "history",
|
|
1316
|
+
"phase": "negotiating",
|
|
1317
|
+
"title": "Card",
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
"actionScope": null,
|
|
1321
|
+
"channel": null,
|
|
1322
|
+
"key": "card.2",
|
|
1323
|
+
"mount": null,
|
|
1324
|
+
"origin": "history",
|
|
1325
|
+
"phase": "expired",
|
|
1326
|
+
"title": "Card",
|
|
1327
|
+
},
|
|
1328
|
+
],
|
|
1270
1329
|
}
|
|
1271
1330
|
`;
|
|
1272
1331
|
|
|
@@ -1284,6 +1343,7 @@ exports[`corpus > 10. empty-turn — status resolves, no empty bubble 1`] = `
|
|
|
1284
1343
|
],
|
|
1285
1344
|
"recovery": null,
|
|
1286
1345
|
"status": null,
|
|
1346
|
+
"views": [],
|
|
1287
1347
|
}
|
|
1288
1348
|
`;
|
|
1289
1349
|
|
|
@@ -1328,6 +1388,7 @@ exports[`corpus > 11. unknown-block-storm — labeled rows; nothing blank, nothi
|
|
|
1328
1388
|
],
|
|
1329
1389
|
"recovery": null,
|
|
1330
1390
|
"status": null,
|
|
1391
|
+
"views": [],
|
|
1331
1392
|
}
|
|
1332
1393
|
`;
|
|
1333
1394
|
|
|
@@ -1392,6 +1453,7 @@ exports[`corpus > 12. interleaved-media-code-citations — ordering and aggregat
|
|
|
1392
1453
|
],
|
|
1393
1454
|
"recovery": null,
|
|
1394
1455
|
"status": null,
|
|
1456
|
+
"views": [],
|
|
1395
1457
|
}
|
|
1396
1458
|
`;
|
|
1397
1459
|
|
|
@@ -1418,6 +1480,7 @@ exports[`corpus > 13. saturated-then-served — only R12 waiting, then a normal
|
|
|
1418
1480
|
],
|
|
1419
1481
|
"recovery": null,
|
|
1420
1482
|
"status": null,
|
|
1483
|
+
"views": [],
|
|
1421
1484
|
}
|
|
1422
1485
|
`;
|
|
1423
1486
|
|
|
@@ -1452,6 +1515,7 @@ exports[`corpus > 14. reasoning-heavy — calm collapses to one line; debug expa
|
|
|
1452
1515
|
],
|
|
1453
1516
|
"recovery": null,
|
|
1454
1517
|
"status": null,
|
|
1518
|
+
"views": [],
|
|
1455
1519
|
}
|
|
1456
1520
|
`;
|
|
1457
1521
|
|
|
@@ -1601,6 +1665,24 @@ exports[`corpus > 15. tools-around-a-view — the group SPLITS at the view's pos
|
|
|
1601
1665
|
],
|
|
1602
1666
|
"recovery": null,
|
|
1603
1667
|
"status": null,
|
|
1668
|
+
"views": [
|
|
1669
|
+
{
|
|
1670
|
+
"actionScope": null,
|
|
1671
|
+
"channel": "inline",
|
|
1672
|
+
"key": "view.t3",
|
|
1673
|
+
"mount": {
|
|
1674
|
+
"channel": "inline",
|
|
1675
|
+
"resource": {
|
|
1676
|
+
"mimeType": "text/html",
|
|
1677
|
+
"text": "<div>picker</div>",
|
|
1678
|
+
"uri": "ui://cards/slot-picker",
|
|
1679
|
+
},
|
|
1680
|
+
},
|
|
1681
|
+
"origin": "live",
|
|
1682
|
+
"phase": "negotiating",
|
|
1683
|
+
"title": "ggui render",
|
|
1684
|
+
},
|
|
1685
|
+
],
|
|
1604
1686
|
}
|
|
1605
1687
|
`;
|
|
1606
1688
|
|
|
@@ -1618,6 +1700,7 @@ exports[`corpus > 16. user-send-failure — R0 failed with retry; the message ne
|
|
|
1618
1700
|
],
|
|
1619
1701
|
"recovery": null,
|
|
1620
1702
|
"status": null,
|
|
1703
|
+
"views": [],
|
|
1621
1704
|
}
|
|
1622
1705
|
`;
|
|
1623
1706
|
|
|
@@ -1739,6 +1822,32 @@ exports[`corpus > 19. persisted-plus-live — the fold owns its trailing turns;
|
|
|
1739
1822
|
"kind": "status",
|
|
1740
1823
|
"state": "using-tool",
|
|
1741
1824
|
},
|
|
1825
|
+
"views": [
|
|
1826
|
+
{
|
|
1827
|
+
"actionScope": "ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c0ffee",
|
|
1828
|
+
"channel": "locator",
|
|
1829
|
+
"key": "view.tA",
|
|
1830
|
+
"mount": {
|
|
1831
|
+
"channel": "locator",
|
|
1832
|
+
"resourceUri": "ui://ggui/render/render_00000000-0000-4000-8000-300000000001/c0ffee",
|
|
1833
|
+
},
|
|
1834
|
+
"origin": "live",
|
|
1835
|
+
"phase": "negotiating",
|
|
1836
|
+
"title": "ggui render",
|
|
1837
|
+
},
|
|
1838
|
+
{
|
|
1839
|
+
"actionScope": "ui://ggui/render/old/1",
|
|
1840
|
+
"channel": "locator",
|
|
1841
|
+
"key": "card.1",
|
|
1842
|
+
"mount": {
|
|
1843
|
+
"channel": "locator",
|
|
1844
|
+
"resourceUri": "ui://ggui/render/old/1",
|
|
1845
|
+
},
|
|
1846
|
+
"origin": "history",
|
|
1847
|
+
"phase": "negotiating",
|
|
1848
|
+
"title": "Card",
|
|
1849
|
+
},
|
|
1850
|
+
],
|
|
1742
1851
|
}
|
|
1743
1852
|
`;
|
|
1744
1853
|
|
|
@@ -1807,6 +1916,7 @@ exports[`corpus > 20. hitl-grant-modes — the persisted declaration renders; an
|
|
|
1807
1916
|
],
|
|
1808
1917
|
"recovery": null,
|
|
1809
1918
|
"status": null,
|
|
1919
|
+
"views": [],
|
|
1810
1920
|
}
|
|
1811
1921
|
`;
|
|
1812
1922
|
|
|
@@ -1849,6 +1959,7 @@ exports[`corpus > 21. notice-rows — both arrival paths render labeled, never a
|
|
|
1849
1959
|
],
|
|
1850
1960
|
"recovery": null,
|
|
1851
1961
|
"status": null,
|
|
1962
|
+
"views": [],
|
|
1852
1963
|
}
|
|
1853
1964
|
`;
|
|
1854
1965
|
|
|
@@ -1912,11 +2023,47 @@ exports[`corpus > 22. promoted-view — the stage's mount chips (guuey#204); oth
|
|
|
1912
2023
|
"key": "view.t2",
|
|
1913
2024
|
"kind": "viewRef",
|
|
1914
2025
|
"label": "ggui render — on canvas",
|
|
2026
|
+
"phase": "negotiating",
|
|
2027
|
+
"selected": true,
|
|
1915
2028
|
"title": "ggui render",
|
|
1916
2029
|
},
|
|
1917
2030
|
],
|
|
1918
2031
|
"recovery": null,
|
|
1919
2032
|
"status": null,
|
|
2033
|
+
"views": [
|
|
2034
|
+
{
|
|
2035
|
+
"actionScope": null,
|
|
2036
|
+
"channel": "inline",
|
|
2037
|
+
"key": "view.t1",
|
|
2038
|
+
"mount": {
|
|
2039
|
+
"channel": "inline",
|
|
2040
|
+
"resource": {
|
|
2041
|
+
"mimeType": "text/html",
|
|
2042
|
+
"text": "<div>t1</div>",
|
|
2043
|
+
"uri": "ui://cards/t1",
|
|
2044
|
+
},
|
|
2045
|
+
},
|
|
2046
|
+
"origin": "live",
|
|
2047
|
+
"phase": "negotiating",
|
|
2048
|
+
"title": "ggui render",
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
"actionScope": null,
|
|
2052
|
+
"channel": "inline",
|
|
2053
|
+
"key": "view.t2",
|
|
2054
|
+
"mount": {
|
|
2055
|
+
"channel": "inline",
|
|
2056
|
+
"resource": {
|
|
2057
|
+
"mimeType": "text/html",
|
|
2058
|
+
"text": "<div>t2</div>",
|
|
2059
|
+
"uri": "ui://cards/t2",
|
|
2060
|
+
},
|
|
2061
|
+
},
|
|
2062
|
+
"origin": "live",
|
|
2063
|
+
"phase": "negotiating",
|
|
2064
|
+
"title": "ggui render",
|
|
2065
|
+
},
|
|
2066
|
+
],
|
|
1920
2067
|
}
|
|
1921
2068
|
`;
|
|
1922
2069
|
|
|
@@ -1962,6 +2109,20 @@ exports[`corpus > 23. meta-less-locator — a \`_meta\`-withholding producer's r
|
|
|
1962
2109
|
],
|
|
1963
2110
|
"recovery": null,
|
|
1964
2111
|
"status": null,
|
|
2112
|
+
"views": [
|
|
2113
|
+
{
|
|
2114
|
+
"actionScope": "ui://ggui/render/render_dark/h1",
|
|
2115
|
+
"channel": "locator",
|
|
2116
|
+
"key": "view.t1",
|
|
2117
|
+
"mount": {
|
|
2118
|
+
"channel": "locator",
|
|
2119
|
+
"resourceUri": "ui://ggui/render/render_dark/h1",
|
|
2120
|
+
},
|
|
2121
|
+
"origin": "live",
|
|
2122
|
+
"phase": "negotiating",
|
|
2123
|
+
"title": "ggui render",
|
|
2124
|
+
},
|
|
2125
|
+
],
|
|
1965
2126
|
}
|
|
1966
2127
|
`;
|
|
1967
2128
|
|
|
@@ -2016,6 +2177,113 @@ exports[`corpus > 24. prod-wire-ggui-render — ggui's production posture: no _m
|
|
|
2016
2177
|
],
|
|
2017
2178
|
"recovery": null,
|
|
2018
2179
|
"status": null,
|
|
2180
|
+
"views": [
|
|
2181
|
+
{
|
|
2182
|
+
"actionScope": "ui://ggui/render/render_5d11b1b4-75ba-44e5-81bb-0b420b86bb8e/7328555b44e72bd7",
|
|
2183
|
+
"channel": "locator",
|
|
2184
|
+
"key": "view.t1",
|
|
2185
|
+
"mount": {
|
|
2186
|
+
"channel": "locator",
|
|
2187
|
+
"resourceUri": "ui://ggui/render/render_5d11b1b4-75ba-44e5-81bb-0b420b86bb8e/7328555b44e72bd7",
|
|
2188
|
+
},
|
|
2189
|
+
"origin": "live",
|
|
2190
|
+
"phase": "negotiating",
|
|
2191
|
+
"title": "ggui render",
|
|
2192
|
+
},
|
|
2193
|
+
],
|
|
2194
|
+
}
|
|
2195
|
+
`;
|
|
2196
|
+
|
|
2197
|
+
exports[`corpus > 25. chips-presentation — EVERY view chips; the promoted key marks the selection; views roster carries the mounts (guuey#301) 1`] = `
|
|
2198
|
+
{
|
|
2199
|
+
"items": [
|
|
2200
|
+
{
|
|
2201
|
+
"expanded": true,
|
|
2202
|
+
"key": "u0",
|
|
2203
|
+
"kind": "user",
|
|
2204
|
+
"retry": false,
|
|
2205
|
+
"state": "sent",
|
|
2206
|
+
"text": "show me two cards",
|
|
2207
|
+
},
|
|
2208
|
+
{
|
|
2209
|
+
"argsPreview": null,
|
|
2210
|
+
"attribution": true,
|
|
2211
|
+
"expanded": false,
|
|
2212
|
+
"key": "tool.t1",
|
|
2213
|
+
"kind": "tool",
|
|
2214
|
+
"name": "ggui_render",
|
|
2215
|
+
"result": null,
|
|
2216
|
+
"state": "done",
|
|
2217
|
+
"title": "ggui render",
|
|
2218
|
+
"toolCallId": "t1",
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
"expanded": false,
|
|
2222
|
+
"key": "view.t1",
|
|
2223
|
+
"kind": "viewRef",
|
|
2224
|
+
"label": "ggui render",
|
|
2225
|
+
"phase": "negotiating",
|
|
2226
|
+
"selected": false,
|
|
2227
|
+
"title": "ggui render",
|
|
2228
|
+
},
|
|
2229
|
+
{
|
|
2230
|
+
"argsPreview": null,
|
|
2231
|
+
"attribution": true,
|
|
2232
|
+
"expanded": false,
|
|
2233
|
+
"key": "tool.t2",
|
|
2234
|
+
"kind": "tool",
|
|
2235
|
+
"name": "ggui_render",
|
|
2236
|
+
"result": null,
|
|
2237
|
+
"state": "done",
|
|
2238
|
+
"title": "ggui render",
|
|
2239
|
+
"toolCallId": "t2",
|
|
2240
|
+
},
|
|
2241
|
+
{
|
|
2242
|
+
"expanded": false,
|
|
2243
|
+
"key": "view.t2",
|
|
2244
|
+
"kind": "viewRef",
|
|
2245
|
+
"label": "ggui render — on canvas",
|
|
2246
|
+
"phase": "negotiating",
|
|
2247
|
+
"selected": true,
|
|
2248
|
+
"title": "ggui render",
|
|
2249
|
+
},
|
|
2250
|
+
],
|
|
2251
|
+
"recovery": null,
|
|
2252
|
+
"status": null,
|
|
2253
|
+
"views": [
|
|
2254
|
+
{
|
|
2255
|
+
"actionScope": null,
|
|
2256
|
+
"channel": "inline",
|
|
2257
|
+
"key": "view.t1",
|
|
2258
|
+
"mount": {
|
|
2259
|
+
"channel": "inline",
|
|
2260
|
+
"resource": {
|
|
2261
|
+
"mimeType": "text/html",
|
|
2262
|
+
"text": "<div>t1</div>",
|
|
2263
|
+
"uri": "ui://cards/t1",
|
|
2264
|
+
},
|
|
2265
|
+
},
|
|
2266
|
+
"origin": "live",
|
|
2267
|
+
"phase": "negotiating",
|
|
2268
|
+
"title": "ggui render",
|
|
2269
|
+
},
|
|
2270
|
+
{
|
|
2271
|
+
"actionScope": null,
|
|
2272
|
+
"channel": "inline",
|
|
2273
|
+
"key": "view.t2",
|
|
2274
|
+
"mount": {
|
|
2275
|
+
"channel": "inline",
|
|
2276
|
+
"resource": {
|
|
2277
|
+
"mimeType": "text/html",
|
|
2278
|
+
"text": "<div>t2</div>",
|
|
2279
|
+
"uri": "ui://cards/t2",
|
|
2280
|
+
},
|
|
2281
|
+
},
|
|
2282
|
+
"origin": "live",
|
|
2283
|
+
"phase": "negotiating",
|
|
2284
|
+
"title": "ggui render",
|
|
2285
|
+
},
|
|
2286
|
+
],
|
|
2019
2287
|
}
|
|
2020
2288
|
`;
|
|
2021
2289
|
|
|
@@ -2098,5 +2366,6 @@ exports[`corpus > 25. oauth-auth-ask — kind:auth + authConfig oauth2 plans as
|
|
|
2098
2366
|
],
|
|
2099
2367
|
"recovery": null,
|
|
2100
2368
|
"status": null,
|
|
2369
|
+
"views": [],
|
|
2101
2370
|
}
|
|
2102
2371
|
`;
|
package/src/index.ts
CHANGED
package/src/plan.ts
CHANGED
|
@@ -48,6 +48,7 @@ import type {
|
|
|
48
48
|
DisplayItem,
|
|
49
49
|
ItemKey,
|
|
50
50
|
NoticeItem,
|
|
51
|
+
PlanViewSummary,
|
|
51
52
|
StatusLineItem,
|
|
52
53
|
ToolGroupItem,
|
|
53
54
|
ToolItem,
|
|
@@ -148,17 +149,43 @@ function foldAssistantSources(
|
|
|
148
149
|
const hasTurnIdentity = result.messages.some(
|
|
149
150
|
(m) => m.role === "assistant" && m.turnId !== undefined,
|
|
150
151
|
);
|
|
152
|
+
// ONE source per conversational TURN, not per assistant message segment
|
|
153
|
+
// (guuey#306 — the mis-slotting bug): a pod fold emits SEVERAL assistant
|
|
154
|
+
// messages per turn (thought/text/tool interleave — a generative-card
|
|
155
|
+
// turn is ~6-8 segments), and the conversation loop pairs users to
|
|
156
|
+
// sources BY SLOT — per-segment sources stretched the index space, so
|
|
157
|
+
// from turn 2 on every user bubble rendered clustered inside turn 1's
|
|
158
|
+
// segment run. Grouping: a `user` row closes the current group (the
|
|
159
|
+
// fold's messages are the whole conversation, users included), and a
|
|
160
|
+
// turnId CHANGE between assistant segments splits too (identity beats
|
|
161
|
+
// adjacency — two turns without an intervening user row still separate).
|
|
151
162
|
const sources: AssistantSource[] = [];
|
|
152
163
|
const notices: FoldNotice[] = [];
|
|
164
|
+
let current: AssistantSource | null = null;
|
|
165
|
+
let currentTurnId: string | undefined;
|
|
153
166
|
for (const m of result.messages) {
|
|
154
|
-
if (m.role === "
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
if (m.role === "user") {
|
|
168
|
+
current = null;
|
|
169
|
+
currentTurnId = undefined;
|
|
170
|
+
} else if (m.role === "assistant") {
|
|
171
|
+
const startsNewTurn =
|
|
172
|
+
current === null ||
|
|
173
|
+
(m.turnId !== undefined && currentTurnId !== undefined && m.turnId !== currentTurnId);
|
|
174
|
+
if (startsNewTurn) {
|
|
175
|
+
current = { blocks: [], live: false, stopped: false };
|
|
176
|
+
sources.push(current);
|
|
177
|
+
currentTurnId = undefined;
|
|
178
|
+
}
|
|
179
|
+
if (m.turnId !== undefined) currentTurnId = m.turnId;
|
|
180
|
+
current!.blocks.push(...m.content);
|
|
181
|
+
if (inFlight && hasTurnIdentity && m.turnId !== undefined && openTurns.has(m.turnId)) {
|
|
182
|
+
current!.live = true;
|
|
183
|
+
}
|
|
160
184
|
} else if (m.role === "tool" && sources.length > 0) {
|
|
161
|
-
|
|
185
|
+
// Tool results belong to the turn in progress — or, when they trail a
|
|
186
|
+
// user boundary before the next assistant segment, to the PRECEDING
|
|
187
|
+
// turn (the pre-#306 last-source semantics, kept).
|
|
188
|
+
(current ?? sources[sources.length - 1]!).blocks.push(...m.content);
|
|
162
189
|
} else if (m.role === "notice") {
|
|
163
190
|
// R16 (spec draft.2): a session annotation — a PEER row, never folded
|
|
164
191
|
// into assistant content; it anchors after the source it followed.
|
|
@@ -177,10 +204,33 @@ function foldAssistantSources(
|
|
|
177
204
|
return { sources, notices };
|
|
178
205
|
}
|
|
179
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Flat assistant rows grouped by conversational ORDER (guuey#306): a user
|
|
209
|
+
* row closes the current slot, consecutive assistant rows merge into one —
|
|
210
|
+
* the assembler's interleave IS the turn structure, and the old
|
|
211
|
+
* filter/zip threw it away (double/empty assistant rows drifted every
|
|
212
|
+
* later user bubble by one). Shared by the flat path and the fold-vs-flat
|
|
213
|
+
* seam, so both count the same slots.
|
|
214
|
+
*/
|
|
215
|
+
function flatSettledGroups(messages: TranscriptInputs["messages"]): AssistantSource[] {
|
|
216
|
+
const groups: AssistantSource[] = [];
|
|
217
|
+
let current: AssistantSource | null = null;
|
|
218
|
+
for (const m of messages) {
|
|
219
|
+
if (m.role === "user") {
|
|
220
|
+
current = null;
|
|
221
|
+
} else if (m.role === "assistant") {
|
|
222
|
+
if (current === null) {
|
|
223
|
+
current = { blocks: [], live: false, stopped: false };
|
|
224
|
+
groups.push(current);
|
|
225
|
+
}
|
|
226
|
+
current.blocks.push({ type: "text", text: m.text });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return groups;
|
|
230
|
+
}
|
|
231
|
+
|
|
180
232
|
function flatAssistantSources(inputs: TranscriptInputs, inFlight: boolean): AssistantSource[] {
|
|
181
|
-
const settled: AssistantSource[] = inputs.messages
|
|
182
|
-
.filter((m) => m.role === "assistant")
|
|
183
|
-
.map((m) => ({ blocks: [{ type: "text", text: m.text }], live: false, stopped: false }));
|
|
233
|
+
const settled: AssistantSource[] = flatSettledGroups(inputs.messages);
|
|
184
234
|
// The in-flight (or abort-kept) partial is its own trailing slot — settled
|
|
185
235
|
// turns live in `messages`; `assistantText` is ignored once `ready` again
|
|
186
236
|
// UNLESS the turn ended by abort (R1 aborted-partial keeps it).
|
|
@@ -726,29 +776,23 @@ export function planTranscript(
|
|
|
726
776
|
// keep rendering as flat text slots in front. When the fold spans the
|
|
727
777
|
// whole conversation (a pure-live session — the corpus's world), the kept
|
|
728
778
|
// prefix is empty and the plan is byte-identical to the old wholesale
|
|
729
|
-
// replace. Alignment
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
// cards by the actionScope dedupe below.
|
|
779
|
+
// replace. Alignment counts TURNS on
|
|
780
|
+
// both sides (guuey#306): fold sources group per turn (user boundary +
|
|
781
|
+
// turnId change), flat entries group by conversational order — the old
|
|
782
|
+
// per-message COUNT approximation (and its one-off seam drift on
|
|
783
|
+
// card-only turns) is gone.
|
|
735
784
|
let assistants: AssistantSource[];
|
|
736
785
|
if (inputs.result) {
|
|
737
786
|
const fold = foldAssistantSources(inputs.result, inFlight, inputs.aborted === true);
|
|
738
787
|
const foldSources = fold.sources;
|
|
739
788
|
const settledFoldCount = foldSources.filter((s) => !s.live).length;
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
stopped: false,
|
|
748
|
-
}),
|
|
749
|
-
),
|
|
750
|
-
...foldSources,
|
|
751
|
-
];
|
|
789
|
+
// Both sides of the seam now count TURNS (guuey#306): fold sources are
|
|
790
|
+
// per-turn groups, and the flat prefix uses the SAME order-derived
|
|
791
|
+
// grouping — the old per-message counts were the documented
|
|
792
|
+
// approximation this replaces.
|
|
793
|
+
const flatGroups = flatSettledGroups(inputs.messages);
|
|
794
|
+
const keep = Math.max(0, flatGroups.length - settledFoldCount);
|
|
795
|
+
assistants = [...flatGroups.slice(0, keep), ...foldSources];
|
|
752
796
|
if (policy.notice.show) {
|
|
753
797
|
// Fold-borne notices anchor to the fold source they followed, which
|
|
754
798
|
// sits at global slot `keep + afterSourceIndex` after the seam.
|
|
@@ -919,26 +963,77 @@ export function planTranscript(
|
|
|
919
963
|
});
|
|
920
964
|
}
|
|
921
965
|
|
|
922
|
-
//
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
//
|
|
926
|
-
|
|
927
|
-
|
|
966
|
+
// The host-canvas contract (guuey#301): every view the plan saw, with
|
|
967
|
+
// its mount material — collected BEFORE any chips/promotion pass, so a
|
|
968
|
+
// stage can render the selected mount even when the transcript shows
|
|
969
|
+
// only chips.
|
|
970
|
+
const views: PlanViewSummary[] = [];
|
|
971
|
+
for (const item of items) {
|
|
972
|
+
if (item.kind !== "view") continue;
|
|
973
|
+
views.push({
|
|
974
|
+
key: item.key,
|
|
975
|
+
title: item.toolTitle ?? policy.strings.viewRefFallbackTitle,
|
|
976
|
+
phase: item.phase,
|
|
977
|
+
channel: item.channel,
|
|
978
|
+
mount: item.mount,
|
|
979
|
+
actionScope: item.actionScope,
|
|
980
|
+
// The kit's own key scheme is authoritative here: `card.{seq}` is
|
|
981
|
+
// exactly the history-card emission (this file builds both).
|
|
982
|
+
origin: item.key.startsWith("card.") ? "history" : "live",
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Two composable behaviors over the assembled items:
|
|
987
|
+
//
|
|
988
|
+
// guuey#204 "promote and reference" (inline presentation): the ONE mount
|
|
989
|
+
// a host-owned stage shows (`inputs.promotedViewKey`) renders as a
|
|
990
|
+
// compact reference chip instead of a second live app instance. A key
|
|
991
|
+
// matching nothing (stale host state) or an expired mount promotes
|
|
992
|
+
// nothing — the honest no-op; the next render's key corrects it.
|
|
993
|
+
//
|
|
994
|
+
// guuey#301 chips presentation (`policy.view.presentation === "chips"`):
|
|
995
|
+
// EVERY view plans as a chip — the full render belongs to the host's
|
|
996
|
+
// stage; the promoted key marks the SELECTED chip (browser-history
|
|
997
|
+
// mechanic), and expired/dead views chip with their honest state.
|
|
998
|
+
//
|
|
999
|
+
// Either way a chip keeps the mount's key, so overrides and phase
|
|
1000
|
+
// reports survive a swap back to the full mount.
|
|
928
1001
|
const promoted = inputs.promotedViewKey;
|
|
1002
|
+
const chips = policy.view.presentation === "chips";
|
|
929
1003
|
const finalItems =
|
|
930
|
-
promoted === undefined
|
|
1004
|
+
!chips && promoted === undefined
|
|
931
1005
|
? items
|
|
932
1006
|
: items.map((item): DisplayItem => {
|
|
933
|
-
if (item.kind !== "view"
|
|
934
|
-
if (item.phase === "expired" || item.mount === null) return item;
|
|
1007
|
+
if (item.kind !== "view") return item;
|
|
935
1008
|
const title = item.toolTitle ?? policy.strings.viewRefFallbackTitle;
|
|
1009
|
+
if (chips) {
|
|
1010
|
+
const mountable = item.phase !== "expired" && item.mount !== null;
|
|
1011
|
+
const selected = mountable && item.key === promoted;
|
|
1012
|
+
const ref: ViewRefItem = {
|
|
1013
|
+
kind: "viewRef",
|
|
1014
|
+
key: item.key,
|
|
1015
|
+
expanded: false,
|
|
1016
|
+
title,
|
|
1017
|
+
label: selected
|
|
1018
|
+
? policy.strings.viewPromoted(title)
|
|
1019
|
+
: mountable
|
|
1020
|
+
? policy.strings.viewChip(title)
|
|
1021
|
+
: policy.strings.viewChipExpired(title),
|
|
1022
|
+
selected,
|
|
1023
|
+
phase: item.phase,
|
|
1024
|
+
};
|
|
1025
|
+
return ref;
|
|
1026
|
+
}
|
|
1027
|
+
if (item.key !== promoted) return item;
|
|
1028
|
+
if (item.phase === "expired" || item.mount === null) return item;
|
|
936
1029
|
const ref: ViewRefItem = {
|
|
937
1030
|
kind: "viewRef",
|
|
938
1031
|
key: item.key,
|
|
939
1032
|
expanded: false,
|
|
940
1033
|
title,
|
|
941
1034
|
label: policy.strings.viewPromoted(title),
|
|
1035
|
+
selected: true,
|
|
1036
|
+
phase: item.phase,
|
|
942
1037
|
};
|
|
943
1038
|
return ref;
|
|
944
1039
|
});
|
|
@@ -948,6 +1043,7 @@ export function planTranscript(
|
|
|
948
1043
|
status: deriveStatus(inputs, policy),
|
|
949
1044
|
recovery:
|
|
950
1045
|
inputs.adopted === true && policy.debugDetail ? policy.strings.recoveredFromHistory : null,
|
|
1046
|
+
views,
|
|
951
1047
|
};
|
|
952
1048
|
}
|
|
953
1049
|
|
package/src/policy.ts
CHANGED
|
@@ -40,8 +40,15 @@ export interface TranscriptPolicy {
|
|
|
40
40
|
/** Preview bound — the plan never carries a full giant payload (fixture 6). */
|
|
41
41
|
previewChars: number;
|
|
42
42
|
};
|
|
43
|
-
/**
|
|
44
|
-
|
|
43
|
+
/**
|
|
44
|
+
* R6 (sandbox overrides pass through to `<GuueyView>` in 3b).
|
|
45
|
+
* `presentation` (guuey#301): `"inline"` = every mount renders as a live
|
|
46
|
+
* view in the transcript (the default, unchanged); `"chips"` = EVERY
|
|
47
|
+
* view plans as a compact {@link ViewRefItem} — the full render belongs
|
|
48
|
+
* to a host-owned stage fed by `TranscriptPlan.views` + the selection
|
|
49
|
+
* input `promotedViewKey`.
|
|
50
|
+
*/
|
|
51
|
+
view: { timeoutMs: number; presentation: "inline" | "chips" };
|
|
45
52
|
/** R7. */
|
|
46
53
|
media: { inlineImageCapRem: number; chipOnly: boolean };
|
|
47
54
|
/** R8. */
|
|
@@ -114,7 +121,7 @@ function calmBase(): TranscriptPolicy {
|
|
|
114
121
|
tool: { expandByDefault: false, argsVisible: false, humanizeTitle: humanizeToolName },
|
|
115
122
|
toolGroup: { threshold: 2 },
|
|
116
123
|
dataResult: { capRem: 16, prettyPrint: true, alwaysShowBytes: false, previewChars: 2048 },
|
|
117
|
-
view: { timeoutMs: 8000 },
|
|
124
|
+
view: { timeoutMs: 8000, presentation: "inline" },
|
|
118
125
|
media: { inlineImageCapRem: 20, chipOnly: false },
|
|
119
126
|
code: { capRem: 16, wrap: false },
|
|
120
127
|
citations: { style: "chips" },
|