@maudecode/talaria-web 1.15.0 → 1.17.2
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/_release.json +4 -4
- package/dist/api/automation-raw.js +6 -3
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +158 -16
- package/dist/api/automation-router.js +12 -6
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +193 -6
- package/dist/api/chat-router.js +39 -16
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +12 -1
- package/dist/api/git-router.js +60 -38
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +6 -0
- package/dist/api/raw-routes.js +35 -58
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +4 -2
- package/dist/api/router.js +8 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +974 -42
- package/dist/api/sessions-router.js +87 -25
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +254 -11
- package/dist/api/settings-router.js +68 -18
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +11 -3
- package/dist/api/sse-routes.js +145 -65
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +6 -2
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +86 -9
- package/dist/api/tools-router.js +19 -15
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +0 -8397
- package/dist/app.js +7 -2
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +11 -4
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +6 -2
- package/dist/auth/oidc.js +16 -11
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/store.d.ts +14 -2
- package/dist/auth/store.js +26 -1
- package/dist/auth/store.js.map +1 -1
- package/dist/cli/dotenv.d.ts +1 -1
- package/dist/cli/dotenv.js +36 -3
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/config/agent-config.d.ts +2 -0
- package/dist/config/agent-config.js +9 -0
- package/dist/config/agent-config.js.map +1 -1
- package/dist/http/addresses.d.ts +6 -0
- package/dist/http/addresses.js +26 -0
- package/dist/http/addresses.js.map +1 -1
- package/dist/http/context.d.ts +6 -3
- package/dist/http/context.js +10 -4
- package/dist/http/context.js.map +1 -1
- package/dist/links.d.ts +26 -0
- package/dist/links.js +60 -0
- package/dist/links.js.map +1 -0
- package/dist/onboarding.js +30 -7
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.js +1 -1
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +167 -5
- package/dist/providers/catalog.js +662 -135
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +9 -4
- package/dist/providers/tables.js.map +1 -1
- package/dist/redact.d.ts +1 -0
- package/dist/redact.js +518 -15
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +79 -37
- package/dist/runtime.js.map +1 -1
- package/dist/sessions/anchor.d.ts +24 -3
- package/dist/sessions/anchor.js +94 -44
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +4 -1
- package/dist/sessions/background-tasks.js +7 -4
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/claude-code.d.ts +19 -0
- package/dist/sessions/claude-code.js +250 -0
- package/dist/sessions/claude-code.js.map +1 -0
- package/dist/sessions/completions.d.ts +13 -1
- package/dist/sessions/completions.js +64 -12
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +14 -1
- package/dist/sessions/compress.js +52 -11
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/journal.d.ts +8 -0
- package/dist/sessions/journal.js +44 -10
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +13 -5
- package/dist/sessions/list.js +56 -26
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +122 -9
- package/dist/sessions/merge.js +544 -33
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +5 -0
- package/dist/sessions/pending.js +30 -10
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/service.d.ts +100 -4
- package/dist/sessions/service.js +567 -102
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +16 -1
- package/dist/sessions/session.js +29 -2
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.js +11 -2
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/state-db.d.ts +26 -4
- package/dist/sessions/state-db.js +126 -8
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.js +6 -1
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +10 -0
- package/dist/sessions/streams.js +14 -3
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/todo.d.ts +6 -1
- package/dist/sessions/todo.js +11 -4
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +12 -1
- package/dist/sessions/tool-display.js +76 -1
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn.d.ts +49 -10
- package/dist/sessions/turn.js +459 -185
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +20 -2
- package/dist/settings.js +49 -2
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.d.ts +2 -2
- package/dist/sidecar/fake.js +7 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.js +2 -1
- package/dist/spa.js.map +1 -1
- package/dist/test/harness.js +2 -0
- package/dist/test/harness.js.map +1 -1
- package/dist/tools/commands.d.ts +15 -0
- package/dist/tools/commands.js +52 -0
- package/dist/tools/commands.js.map +1 -0
- package/dist/tools/crons.d.ts +4 -1
- package/dist/tools/crons.js +58 -11
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/insights.d.ts +8 -2
- package/dist/tools/insights.js +18 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/kanban.d.ts +41 -1
- package/dist/tools/kanban.js +113 -2
- package/dist/tools/kanban.js.map +1 -1
- package/dist/tools/mcp.d.ts +2 -1
- package/dist/tools/mcp.js +16 -6
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/terminal.d.ts +1 -0
- package/dist/tools/terminal.js +1 -1
- package/dist/tools/terminal.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +1 -1
- package/dist/tools/update-notifications.js +1 -1
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +15 -3
- package/dist/tools/updates.js +207 -41
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/fs.d.ts +7 -3
- package/dist/workspace/fs.js +5 -1
- package/dist/workspace/fs.js.map +1 -1
- package/dist/workspace/git.d.ts +57 -10
- package/dist/workspace/git.js +302 -195
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media-refs.d.ts +13 -0
- package/dist/workspace/media-refs.js +165 -0
- package/dist/workspace/media-refs.js.map +1 -0
- package/dist/workspace/media.d.ts +19 -0
- package/dist/workspace/media.js +63 -2
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.d.ts +28 -0
- package/dist/workspace/preview.js +110 -0
- package/dist/workspace/preview.js.map +1 -0
- package/dist/workspace/rollback.d.ts +5 -4
- package/dist/workspace/rollback.js +12 -9
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/upload.d.ts +2 -0
- package/dist/workspace/upload.js +4 -1
- package/dist/workspace/upload.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +7 -1
- package/dist/workspace/workspaces.js +30 -21
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -4
- package/dist/workspace/worktrees.js +28 -33
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +4 -2
- package/sidecar/scripts/replay_sidecar.py +4 -2
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/__main__.py +7 -1
- package/sidecar/talaria_sidecar/home.py +98 -2
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +19 -3
- package/sidecar/talaria_sidecar/methods/chat.py +551 -61
- package/sidecar/talaria_sidecar/methods/commands.py +61 -33
- package/sidecar/talaria_sidecar/methods/config.py +14 -10
- package/sidecar/talaria_sidecar/methods/cron.py +10 -6
- package/sidecar/talaria_sidecar/methods/kanban.py +115 -15
- package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
- package/sidecar/talaria_sidecar/methods/process.py +60 -1
- package/sidecar/talaria_sidecar/methods/profiles.py +12 -5
- package/sidecar/talaria_sidecar/methods/providers.py +161 -1
- package/sidecar/talaria_sidecar/methods/runtime.py +5 -0
- package/sidecar/talaria_sidecar/methods/text.py +18 -21
- package/sidecar/talaria_sidecar/methods/usage.py +264 -5
- package/static/dist/FILES.txt +126 -121
- package/static/dist/assets/AppShell-fb2td9zi.js +1 -0
- package/static/dist/assets/ChatPage-cj96kiy9.js +25 -0
- package/static/dist/assets/Dialog-kxcg7ard.js +1 -0
- package/static/dist/assets/ExtensionRoute-jf7h0754.js +1 -0
- package/static/dist/assets/HubRoute-gmanpd5y.js +2 -0
- package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-mdwlvj3w.js} +125 -125
- package/static/dist/assets/RightPanel-ftvrjlcp.js +1 -0
- package/static/dist/assets/Select-bilaypej.js +1 -0
- package/static/dist/assets/SessionListPanel-og8n36qs.js +1 -0
- package/static/dist/assets/SettingsLayout-jonjefmj.js +1 -0
- package/static/dist/assets/States-nmejrwrm.js +1 -0
- package/static/dist/assets/Toaster-nnl8zkt9.js +1 -0
- package/static/dist/assets/_app-gdidzd94.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-opyh9y0v.js +1 -0
- package/static/dist/assets/_app.index-hbarzbjx.js +1 -0
- package/static/dist/assets/_app.insights-luywlarz.js +1 -0
- package/static/dist/assets/_app.kanban-kincnbr8.js +1 -0
- package/static/dist/assets/_app.logs-oi2gr3gs.js +1 -0
- package/static/dist/assets/_app.memory-b6jfivt6.js +1 -0
- package/static/dist/assets/_app.profiles-lpvixmqp.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-bfd4h3id.js} +1 -1
- package/static/dist/assets/_app.settings-vowx3iq0.js +1 -0
- package/static/dist/assets/_app.settings._section-cnntg1tu.js +3 -0
- package/static/dist/assets/_app.skills-fb57lpau.js +1 -0
- package/static/dist/assets/_app.tasks-eszpx1q7.js +2 -0
- package/static/dist/assets/_app.todos-nr05ac6w.js +1 -0
- package/static/dist/assets/_app.workspaces-d2luomu6.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-b2x1lqot.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-h38o5l7b.js +36 -0
- package/static/dist/assets/{back-e4m6nsnm.js → back-bemaer09.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-bqquxv2u.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-f3fb8w6z.js} +1 -1
- package/static/dist/assets/cancel-drd0g6d9.js +1 -0
- package/static/dist/assets/channel-cpdgf867.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-bzfyyzp3.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-ly99rfx5.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-bg6ecfqf.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-e8k7mmqw.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-cmalnoaj.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gmif5o01.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-f1nwwh6u.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-oktfh5v7.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-b1sdx96z.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-fhu4nzxt.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-em31rnef.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-bxhhyftw.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-fmrvwcav.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-lymukl2j.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-lmza6k23.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +1 -0
- package/static/dist/assets/{copied-kq0u3ht7.js → copied-ima3pysb.js} +1 -1
- package/static/dist/assets/copy-fw5b1pc6.js +1 -0
- package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-czlhbyhs.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-gq7mvswb.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-cqdbf7dr.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-o6arxa58.js} +3 -3
- package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-nhnd68tz.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-dk2sclzn.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-n3k5nyx3.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m6q9cwdh.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-lsa5641p.js} +1 -1
- package/static/dist/assets/endpoints-d33siokx.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fo2ckbe6.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-nzqz6x9g.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-gukvcxe6.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-lqhdp8f0.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-igw6cp39.js} +1 -1
- package/static/dist/assets/index-cxq58xb1.js +11 -0
- package/static/dist/assets/index-hgtx08n5.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-gvu7a93l.js} +1 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-m8t8g6w1.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-l07vkbgj.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-hybkgj8l.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-by13uvtn.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-b2eg0fws.js → lazyRouteComponent-af0lvgjx.js} +1 -1
- package/static/dist/assets/{line-friyjof3.js → line-osxq6fq7.js} +1 -1
- package/static/dist/assets/link-jyxicdhg.js +1 -0
- package/static/dist/assets/login-ec21t39x.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-i91oxmou.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-m82mx8ww.js} +1 -1
- package/static/dist/assets/onboarding-btnltvyz.js +1 -0
- package/static/dist/assets/onboarding_model_label-dp7xffoh.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-nbtgge2o.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-f1zxzga1.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-of8vuhxs.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-o7y8ftio.js} +3 -3
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-iqiwia1t.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-delnsy2v.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-nspnyq9u.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-z41tqx5z.js} +1 -1
- package/static/dist/assets/refresh-b4uams1i.js +1 -0
- package/static/dist/assets/refresh-cw-nt1dtsx4.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-bdvif194.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-h73e0rol.js} +1 -1
- package/static/dist/assets/{saved-do49si3v.js → saved-kg52csy7.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-iv95mb1z.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ej0iapxe.js} +1 -1
- package/static/dist/assets/share._token-f97e94r0.js +2 -0
- package/static/dist/assets/sliders-horizontal-fj18it47.js +1 -0
- package/static/dist/assets/sse-fwpqtzbg.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-k417ixmf.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-esola70w.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-getrz2y8.js} +1 -1
- package/static/dist/assets/todoStore-eginf9h4.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-hc7ap1q7.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-b7k1syy5.js} +1 -1
- package/static/dist/assets/tts-lxdxnmjd.js +1 -0
- package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-ovpfthg4.js} +1 -1
- package/static/dist/assets/useForm-bcblv1rx.js +1 -0
- package/static/dist/assets/useLocale-fi5jb8ma.js +1 -0
- package/static/dist/assets/{useParams-lo9u33d1.js → useParams-lhg566ew.js} +1 -1
- package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-lbrh6ejn.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-n6pcy5qb.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-lpa18vz1.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-kpclwwg7.js} +2 -2
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-paa2n8y0.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +2 -2
- package/static/dist/assets/AppShell-orat1s43.js +0 -1
- package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
- package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
- package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
- package/static/dist/assets/Select-fswf66up.js +0 -1
- package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
- package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
- package/static/dist/assets/States-kxyu50at.js +0 -1
- package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
- package/static/dist/assets/_app-fowyruiu.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
- package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
- package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
- package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
- package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
- package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
- package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
- package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
- package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
- package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
- package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
- package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
- package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
- package/static/dist/assets/channel-gozy3dp3.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
- package/static/dist/assets/endpoints-k8agnro8.js +0 -9
- package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
- package/static/dist/assets/index-nfy0ezs6.css +0 -2
- package/static/dist/assets/index-nqed3oum.js +0 -11
- package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
- package/static/dist/assets/link-m90ahx3n.js +0 -1
- package/static/dist/assets/login-bw7m2fzo.js +0 -1
- package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
- package/static/dist/assets/onboarding-ret37j1e.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
- package/static/dist/assets/refresh-cw-k66keho6.js +0 -1
- package/static/dist/assets/search-j5ki6a9o.js +0 -1
- package/static/dist/assets/share._token-kg8w647b.js +0 -2
- package/static/dist/assets/sliders-horizontal-c4wya4dy.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
- package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
- package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
- package/static/dist/assets/useForm-ocxu460f.js +0 -1
- package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
package/dist/sessions/merge.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* `_merge_display_messages_after_agent_result`, `_message_identity`,
|
|
4
4
|
* `_extract_tool_calls_from_messages`, `_build_partial_message`).
|
|
5
5
|
*/
|
|
6
|
-
import { buildActiveTurnToken } from '../redact.js';
|
|
6
|
+
import { buildActiveTurnToken, completedToolIndex } from '../redact.js';
|
|
7
|
+
import { RunJournal } from './journal.js';
|
|
7
8
|
import { str } from '../util.js';
|
|
8
9
|
import { stripAttachedFilesMarker } from './session.js';
|
|
10
|
+
import { decidedEditDiff, toolEditDiff, toolName } from './tool-display.js';
|
|
9
11
|
export const WORKSPACE_PREFIX_RE = /^\s*\[Workspace::v1:\s*(?:\\.|[^\]\\])+\]\s*/;
|
|
10
12
|
const LEGACY_WORKSPACE_PREFIX_RE = /^\s*\[Workspace:[^\]]+\]\s*/;
|
|
11
13
|
const TOOL_RESULT_SNIPPET_MAX = 4000;
|
|
@@ -55,7 +57,7 @@ export function messageText(content) {
|
|
|
55
57
|
}
|
|
56
58
|
return str(content);
|
|
57
59
|
}
|
|
58
|
-
export function messageIdentity(msg) {
|
|
60
|
+
export function messageIdentity(msg, textLimit = 500) {
|
|
59
61
|
if (!isDict(msg))
|
|
60
62
|
return null;
|
|
61
63
|
const role = str(msg.role);
|
|
@@ -75,7 +77,7 @@ export function messageIdentity(msg) {
|
|
|
75
77
|
return null;
|
|
76
78
|
}
|
|
77
79
|
const toolCalls = Array.isArray(msg.tool_calls) ? msg.tool_calls : [];
|
|
78
|
-
return JSON.stringify([role, text.split(/\s+/).join(' ').trim().slice(0,
|
|
80
|
+
return JSON.stringify([role, text.split(/\s+/).join(' ').trim().slice(0, textLimit), str(msg.tool_call_id), JSON.stringify(sortKeysDeep(toolCalls))]);
|
|
79
81
|
}
|
|
80
82
|
function sortKeysDeep(value) {
|
|
81
83
|
if (Array.isArray(value))
|
|
@@ -96,13 +98,43 @@ export function messagesHavePrefix(messages, prefix) {
|
|
|
96
98
|
return false;
|
|
97
99
|
return true;
|
|
98
100
|
}
|
|
99
|
-
|
|
101
|
+
const PRESERVED_TASK_LIST_PREFIX = '[your active task list was preserved across context compression]';
|
|
102
|
+
const hasPrefix = (text, prefix) => text.slice(0, prefix.length).toLowerCase() === prefix;
|
|
103
|
+
/**
|
|
104
|
+
* TAL-305: the one rule for the marker rows the Agent writes around context compaction. A flagged row, or a user or
|
|
105
|
+
* assistant row starting `[context compaction`, is a compaction summary; a user row starting with the preserved-task-list
|
|
106
|
+
* line is that list. Both ignore case and leading whitespace; prose that merely mentions compaction is no marker.
|
|
107
|
+
*/
|
|
108
|
+
export function markerKind(msg) {
|
|
100
109
|
if (!isDict(msg))
|
|
101
|
-
return
|
|
110
|
+
return null;
|
|
102
111
|
if (msg._context_compression_marker || msg._compression_marker || msg._compaction_marker)
|
|
103
|
-
return
|
|
104
|
-
const
|
|
105
|
-
|
|
112
|
+
return 'context_compaction';
|
|
113
|
+
const role = str(msg.role);
|
|
114
|
+
const text = messageText(msg.content).trimStart();
|
|
115
|
+
if (role === 'user' && hasPrefix(text, PRESERVED_TASK_LIST_PREFIX))
|
|
116
|
+
return 'preserved_task_list';
|
|
117
|
+
if ((role === 'user' || role === 'assistant') && hasPrefix(text, '[context compaction'))
|
|
118
|
+
return 'context_compaction';
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
export function isContextCompressionMarker(msg) {
|
|
122
|
+
return markerKind(msg) === 'context_compaction';
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* TAL-305: stamps `_marker_kind` on marker rows, and `_marker_body` (the list without its marker line) on a preserved
|
|
126
|
+
* task list, so clients render a marker card instead of a message. Returns copies; stored rows are untouched.
|
|
127
|
+
*/
|
|
128
|
+
export function withMarkerKinds(messages) {
|
|
129
|
+
return messages.map((m) => {
|
|
130
|
+
const kind = markerKind(m);
|
|
131
|
+
if (!kind || !isDict(m))
|
|
132
|
+
return m;
|
|
133
|
+
if (kind === 'context_compaction')
|
|
134
|
+
return { ...m, _marker_kind: kind };
|
|
135
|
+
const body = messageText(m.content).trim().slice(PRESERVED_TASK_LIST_PREFIX.length).trim();
|
|
136
|
+
return { ...m, _marker_kind: kind, _marker_body: body };
|
|
137
|
+
});
|
|
106
138
|
}
|
|
107
139
|
/** A user prompt as the user typed it: without the workspace prefix and the attached-files line the server adds. */
|
|
108
140
|
const userPromptText = (text) => stripWorkspacePrefix(stripAttachedFilesMarker(text), true);
|
|
@@ -196,6 +228,17 @@ export function injectMaxIterationSummaryFallback(messages, finalResponse) {
|
|
|
196
228
|
return messages;
|
|
197
229
|
return [...messages, { role: 'assistant', content: finalResponse, _max_iteration_summary_fallback: true }];
|
|
198
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* TAL-537: the user row the Agent appends to ask for that summary is its own prompt, neither shown nor sent back. It is
|
|
233
|
+
* the exhausted turn's last user row; any other row with the same text (one the user typed) stays.
|
|
234
|
+
*/
|
|
235
|
+
export function withoutMaxIterationSummaryRequest(messages, request, msgText) {
|
|
236
|
+
const at = messages.findLastIndex((m) => m.role === 'user');
|
|
237
|
+
const row = messages[at];
|
|
238
|
+
if (!request || row?.content !== request || looksLikeCurrentUserTurn(row, msgText))
|
|
239
|
+
return messages;
|
|
240
|
+
return messages.filter((_, i) => i !== at);
|
|
241
|
+
}
|
|
199
242
|
export function mergeDisplayMessagesAfterAgentResult(previousDisplay, previousContext, resultMessages, msgText, opts = {}) {
|
|
200
243
|
const display = previousDisplay.filter((m) => !isContextCompressionMarker(m));
|
|
201
244
|
const seenPartial = new Set();
|
|
@@ -314,10 +357,134 @@ function presentError(v) {
|
|
|
314
357
|
return v.length > 0;
|
|
315
358
|
return isDict(v) ? Object.keys(v).length > 0 : v === true;
|
|
316
359
|
}
|
|
360
|
+
const RESULT_TEXT_KEYS = ['result', 'results', 'preview', 'content', 'text', 'message', 'summary', 'data', 'items'];
|
|
361
|
+
const RESULT_VIEW_MAX_DEPTH = 8;
|
|
362
|
+
const tryJson = (text) => { try {
|
|
363
|
+
return JSON.parse(text);
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return undefined;
|
|
367
|
+
} };
|
|
368
|
+
const prettyJson = (value) => { try {
|
|
369
|
+
return JSON.stringify(value, null, 2);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return str(value);
|
|
373
|
+
} };
|
|
374
|
+
/** Line breaks and tabs a JSON-encoded result left escaped (`\n`, `\\n`), as the characters they stand for. */
|
|
375
|
+
const unescapeResultText = (text) => text.replace(/\r\n/g, '\n').replace(/\\{1,2}r\\{1,2}n|\\{1,2}n/g, '\n').replace(/\\{1,2}t/g, '\t');
|
|
376
|
+
/** The JSON a result string holds: as written, else with its escaped quotes undone; nested JSON strings unwrap three levels. */
|
|
377
|
+
function resultJson(text) {
|
|
378
|
+
const trimmed = text.trim();
|
|
379
|
+
let value = tryJson(trimmed);
|
|
380
|
+
if (value === undefined && trimmed.includes('\\"'))
|
|
381
|
+
value = tryJson(trimmed.replaceAll('\\"', '"'));
|
|
382
|
+
for (let i = 0; i < 3 && typeof value === 'string'; i += 1) {
|
|
383
|
+
const inner = tryJson(value.trim());
|
|
384
|
+
if (inner === undefined)
|
|
385
|
+
break;
|
|
386
|
+
value = inner;
|
|
387
|
+
}
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
390
|
+
/** A section's text: a string unescaped, a scalar as written, a structure as pretty JSON; `undefined` when blank. */
|
|
391
|
+
function sectionText(value) {
|
|
392
|
+
const text = typeof value === 'string' ? unescapeResultText(value) : typeof value === 'number' || typeof value === 'boolean' ? String(value) : value === null || value === undefined ? '' : prettyJson(value);
|
|
393
|
+
return text.trim() || undefined;
|
|
394
|
+
}
|
|
395
|
+
/** An exit code: an integer, or a string of one; anything else is dropped. */
|
|
396
|
+
function exitCodeOf(value) {
|
|
397
|
+
const n = typeof value === 'number' ? Math.trunc(value) : typeof value === 'string' && /^\s*[+-]?\d+\s*$/.test(value) ? Number(value) : NaN;
|
|
398
|
+
return Number.isSafeInteger(n) ? n : undefined;
|
|
399
|
+
}
|
|
400
|
+
/** A terminal result's sections; the exit code only when it is non-zero or nothing else would show. */
|
|
401
|
+
function terminalView(data) {
|
|
402
|
+
const view = {};
|
|
403
|
+
const stdout = sectionText(data.output) ?? sectionText(data.stdout);
|
|
404
|
+
const stderr = sectionText(data.stderr);
|
|
405
|
+
const error = sectionText(data.error);
|
|
406
|
+
if (stdout)
|
|
407
|
+
view.stdout = stdout;
|
|
408
|
+
if (stderr)
|
|
409
|
+
view.stderr = stderr;
|
|
410
|
+
if (error)
|
|
411
|
+
view.error = error;
|
|
412
|
+
const exit = exitCodeOf(data.exit_code ?? data.exitCode);
|
|
413
|
+
if (exit !== undefined && (exit !== 0 || !Object.keys(view).length))
|
|
414
|
+
view.exit_code = exit;
|
|
415
|
+
return view;
|
|
416
|
+
}
|
|
417
|
+
/** Every item of a list is a text part (`text`, `content` or `message`): their texts, one per line. */
|
|
418
|
+
function textParts(items) {
|
|
419
|
+
const texts = items.map((item) => (isDict(item) ? sectionText(item.text) ?? sectionText(item.content) ?? sectionText(item.message) : undefined));
|
|
420
|
+
return texts.length && texts.every(Boolean) ? texts.join('\n') : undefined;
|
|
421
|
+
}
|
|
422
|
+
const isEmptyView = (view) => Object.keys(view).length === 0;
|
|
423
|
+
function resultView(value, depth) {
|
|
424
|
+
if (typeof value === 'string') {
|
|
425
|
+
const parsed = depth < RESULT_VIEW_MAX_DEPTH ? resultJson(value) : undefined;
|
|
426
|
+
if (parsed !== undefined && typeof parsed !== 'string') {
|
|
427
|
+
const view = resultView(parsed, depth + 1);
|
|
428
|
+
if (!isEmptyView(view))
|
|
429
|
+
return view;
|
|
430
|
+
}
|
|
431
|
+
// Top-level text that is not JSON shows exactly as written; strings inside JSON are unescaped.
|
|
432
|
+
const text = depth === 0 && parsed === undefined ? value.trim() : sectionText(typeof parsed === 'string' ? parsed : value);
|
|
433
|
+
return text ? { text } : {};
|
|
434
|
+
}
|
|
435
|
+
if (Array.isArray(value)) {
|
|
436
|
+
const text = textParts(value) ?? sectionText(value);
|
|
437
|
+
return text ? { text } : {};
|
|
438
|
+
}
|
|
439
|
+
if (!isDict(value)) {
|
|
440
|
+
const text = sectionText(value);
|
|
441
|
+
return text ? { text } : {};
|
|
442
|
+
}
|
|
443
|
+
if (depth >= RESULT_VIEW_MAX_DEPTH)
|
|
444
|
+
return { text: prettyJson(value) };
|
|
445
|
+
// A process result: its output streams, error and exit code.
|
|
446
|
+
if (sectionText(value.output) ?? sectionText(value.stdout) ?? sectionText(value.stderr))
|
|
447
|
+
return terminalView(value);
|
|
448
|
+
for (const key of RESULT_TEXT_KEYS) {
|
|
449
|
+
const view = resultView(value[key], depth + 1);
|
|
450
|
+
if (!isEmptyView(view))
|
|
451
|
+
return view;
|
|
452
|
+
}
|
|
453
|
+
const terminal = terminalView(value);
|
|
454
|
+
if (!isEmptyView(terminal))
|
|
455
|
+
return terminal;
|
|
456
|
+
return Object.keys(value).length ? { text: prettyJson(value) } : {};
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* The server's one result view rule (TAL-315), ported from the iOS formatter so every client renders the same sections:
|
|
460
|
+
* a JSON result (nested JSON strings unwrapped, escaped line breaks undone) with `output`/`stdout`/`stderr` maps to its
|
|
461
|
+
* terminal sections; any other object shows the first readable `result`, `results`, `preview`, `content`, `text`,
|
|
462
|
+
* `message`, `summary`, `data` or `items`, else its `error` and exit code, else pretty JSON. A list of text parts joins
|
|
463
|
+
* by line; a scalar shows as text, and a result that is not JSON exactly as written. Each field is capped like the snippet
|
|
464
|
+
* (`limit`; TAL-331's whole-result read passes `Infinity`).
|
|
465
|
+
*/
|
|
466
|
+
export function toolResultView(raw, limit = TOOL_RESULT_SNIPPET_MAX) {
|
|
467
|
+
// Content parts read as their text; a list with no text (`[{ title }]`) is the result itself.
|
|
468
|
+
const flat = Array.isArray(raw) ? messageText(raw) : raw;
|
|
469
|
+
const view = resultView(Array.isArray(raw) && !str(flat).trim() ? raw : flat, 0);
|
|
470
|
+
return Object.fromEntries(Object.entries(view).map(([key, field]) => [key, typeof field === 'string' ? toolResultSnippet(field, limit) : field]));
|
|
471
|
+
}
|
|
472
|
+
/** A view the server already decided (a built scene row, or one stored with its scene): its known fields, capped. */
|
|
473
|
+
export function decidedResultView(value) {
|
|
474
|
+
const view = {};
|
|
475
|
+
for (const key of ['text', 'stdout', 'stderr', 'error']) {
|
|
476
|
+
const field = value[key];
|
|
477
|
+
if (typeof field === 'string' && field)
|
|
478
|
+
view[key] = toolResultSnippet(field);
|
|
479
|
+
}
|
|
480
|
+
if (typeof value.exit_code === 'number' && Number.isSafeInteger(value.exit_code))
|
|
481
|
+
view.exit_code = value.exit_code;
|
|
482
|
+
return view;
|
|
483
|
+
}
|
|
317
484
|
/**
|
|
318
485
|
* The server's one tool outcome rule (TAL-313), for a live `raw_result` and a persisted tool-role content alike: a result
|
|
319
486
|
* is an error when it parses to an object with a non-empty `error`, a non-zero numeric `exit_code` / `exitCode`, or
|
|
320
|
-
* `success: false`. `result_text` is the result's display snippet.
|
|
487
|
+
* `success: false`. `result_text` is the result's display snippet; `result_view` its display sections (TAL-315).
|
|
321
488
|
*/
|
|
322
489
|
export function toolOutcome(raw) {
|
|
323
490
|
const value = Array.isArray(raw) ? messageText(raw) : raw;
|
|
@@ -332,7 +499,7 @@ export function toolOutcome(raw) {
|
|
|
332
499
|
}
|
|
333
500
|
const exit = isDict(data) ? data.exit_code ?? data.exitCode : undefined;
|
|
334
501
|
const isError = isDict(data) && (presentError(data.error) || (typeof exit === 'number' && exit !== 0) || data.success === false);
|
|
335
|
-
return { is_error: isError, result_text: toolResultSnippet(value) };
|
|
502
|
+
return { is_error: isError, result_text: toolResultSnippet(value), result_view: toolResultView(raw) };
|
|
336
503
|
}
|
|
337
504
|
const finiteOrNull = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
338
505
|
function nearestAssistantIdx(messages, msgIdx) {
|
|
@@ -444,7 +611,8 @@ export function extractToolCallsFromMessages(messages, liveToolCalls = [], recor
|
|
|
444
611
|
* a call only the session-level list recorded joins the row at its `assistant_msg_idx`, a failed or cancelled turn's
|
|
445
612
|
* partial snapshot shows the live calls it kept, and every call carries `done`
|
|
446
613
|
* (answered, or outside the running turn), `is_error` (`toolOutcome` of its result, else the recorded value), `duration`
|
|
447
|
-
* (recorded live, else `null`)
|
|
614
|
+
* (recorded live, else `null`), `result` (the result snippet, else `null`) and `result_view` (its display sections, TAL-315,
|
|
615
|
+
* else `null`). Returns copies; stored rows are untouched.
|
|
448
616
|
*/
|
|
449
617
|
export function withToolCallOutcomes(messages, sessionToolCalls, activeTurnId) {
|
|
450
618
|
// Every identifier a stored call may carry (`ToolCallSchema`).
|
|
@@ -515,25 +683,70 @@ export function withToolCallOutcomes(messages, sessionToolCalls, activeTurnId) {
|
|
|
515
683
|
if (!calls.length && !records.length)
|
|
516
684
|
return m;
|
|
517
685
|
const running = Boolean(activeTurnId) && m._turn_id === activeTurnId;
|
|
518
|
-
const resolve = (id, rec, answered) => {
|
|
686
|
+
const resolve = (id, name, rec, answered) => {
|
|
519
687
|
const reply = id ? replies.get(key(index, id)) : undefined;
|
|
520
688
|
const outcome = reply ? { ...toolOutcome(reply.content), flagged: reply.is_error } : null;
|
|
689
|
+
// TAL-448: a file edit's change, from its full reply, else the one its live record decided.
|
|
690
|
+
const editDiff = reply ? toolEditDiff(name, reply.content) : decidedEditDiff(rec?.edit_diff);
|
|
521
691
|
return {
|
|
692
|
+
...(editDiff ? { edit_diff: editDiff } : {}),
|
|
522
693
|
done: Boolean(reply) || answered || !running, is_error: outcome ? outcome.flagged || outcome.is_error : rec?.is_error === true,
|
|
523
694
|
duration: finiteOrNull(rec?.duration), result: outcome ? outcome.result_text : typeof rec?.snippet === 'string' ? rec.snippet : null,
|
|
695
|
+
result_view: outcome ? outcome.result_view : isDict(rec?.result_view) ? decidedResultView(rec.result_view) : typeof rec?.snippet === 'string' ? toolResultView(rec.snippet) : null,
|
|
524
696
|
};
|
|
525
697
|
};
|
|
526
698
|
const projected = [
|
|
527
|
-
...calls.map((call) => (isDict(call) ? { ...call, ...resolve(callId(call), recorded.get(callId(call)), false) } : call)),
|
|
699
|
+
...calls.map((call) => (isDict(call) ? { ...call, ...resolve(callId(call), toolName(call), recorded.get(callId(call)), false) } : call)),
|
|
528
700
|
...records.map((tc) => ({
|
|
529
701
|
...(str(tc.tid) ? { id: str(tc.tid) } : {}), type: 'function', function: { name: str(tc.name), arguments: JSON.stringify(tc.args ?? {}) },
|
|
530
702
|
// A session-level entry was answered; a live call says whether it completed.
|
|
531
|
-
...resolve(str(tc.tid), tc, typeof tc.done === 'boolean' ? tc.done : true),
|
|
703
|
+
...resolve(str(tc.tid), tc.name, tc, typeof tc.done === 'boolean' ? tc.done : true),
|
|
532
704
|
})),
|
|
533
705
|
];
|
|
534
706
|
return { ...m, tool_calls: projected };
|
|
535
707
|
});
|
|
536
708
|
}
|
|
709
|
+
/** The prompt text naming every attached file by path after it (TAL-276), so an attachment-only turn still asks something. */
|
|
710
|
+
export function attachedFilesPrompt(text, attachments) {
|
|
711
|
+
const named = attachments.map((att) => (isDict(att) ? str(att.path).trim() : '')).filter(Boolean).map(escapeWorkspacePrefixPath);
|
|
712
|
+
return named.length ? `${text}\n\n[Attached files: ${named.join(', ')}]` : text;
|
|
713
|
+
}
|
|
714
|
+
const TOOL_IMAGE_PART_TYPES = new Set(['image', 'image_url', 'input_image']);
|
|
715
|
+
/**
|
|
716
|
+
* TAL-544, Python `_compact_image_parts_for_persistence`: a settled turn's tool-result images (browser and vision
|
|
717
|
+
* screenshots) become `[screenshot]` text, so saves, loads and later turns stop carrying the base64. A result is a `tool`
|
|
718
|
+
* row or an Anthropic-style `tool_result` block in a user row; the user's own attachments stay.
|
|
719
|
+
*/
|
|
720
|
+
export function withoutToolImages(messages) {
|
|
721
|
+
const isImage = (p) => isDict(p) && TOOL_IMAGE_PART_TYPES.has(p.type);
|
|
722
|
+
const hasImage = (parts) => Array.isArray(parts) && parts.some(isImage);
|
|
723
|
+
const compact = (parts) => parts.map((p) => (isImage(p) ? { type: 'text', text: '[screenshot]' } : p));
|
|
724
|
+
const isImageResult = (p) => isDict(p) && p.type === 'tool_result' && hasImage(p.content);
|
|
725
|
+
const out = messages.map((m) => {
|
|
726
|
+
const content = m.content;
|
|
727
|
+
if (m.role === 'tool' && hasImage(content))
|
|
728
|
+
return { ...m, content: compact(content) };
|
|
729
|
+
if (m.role === 'user' && Array.isArray(content) && content.some(isImageResult)) {
|
|
730
|
+
return { ...m, content: content.map((p) => (isImageResult(p) ? { ...p, content: compact(p.content) } : p)) };
|
|
731
|
+
}
|
|
732
|
+
return m;
|
|
733
|
+
});
|
|
734
|
+
// The same array when nothing changed, so a caller can tell whether its rows were rewritten.
|
|
735
|
+
return out.some((m, i) => m !== messages[i]) ? out : messages;
|
|
736
|
+
}
|
|
737
|
+
/** A settled model context without an assistant row repeated back to back, and without tool-result images. */
|
|
738
|
+
export function dedupeContext(messages) {
|
|
739
|
+
const out = [];
|
|
740
|
+
let lastKey = null;
|
|
741
|
+
for (const m of withoutToolImages(messages)) {
|
|
742
|
+
const key = messageIdentity(m);
|
|
743
|
+
if (key !== null && key === lastKey && m.role === 'assistant')
|
|
744
|
+
continue;
|
|
745
|
+
out.push(m);
|
|
746
|
+
lastKey = key;
|
|
747
|
+
}
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
537
750
|
/** Python `_build_partial_message`. */
|
|
538
751
|
export function buildPartialMessage(contentText, reasoningText, toolCalls, now = Date.now() / 1000) {
|
|
539
752
|
let stripped = '';
|
|
@@ -553,21 +766,196 @@ export function buildPartialMessage(contentText, reasoningText, toolCalls, now =
|
|
|
553
766
|
msg._partial_tool_calls = [...toolCalls];
|
|
554
767
|
return msg;
|
|
555
768
|
}
|
|
556
|
-
|
|
557
|
-
|
|
769
|
+
/**
|
|
770
|
+
* TAL-536: a dead run's journaled output as partial rows, in order: each row is a stretch of reasoning and prose with the
|
|
771
|
+
* tool calls that followed it, the shape a Stop's snapshot keeps. A run that ended in `done` gets its answer as a settled
|
|
772
|
+
* row: its last prose row, else the one the `done` frame's session carries (an answer that never streamed, like the
|
|
773
|
+
* tool-limit summary). `answered` says the run finished with one.
|
|
774
|
+
*/
|
|
775
|
+
export function journalOutputRows(events, turnId) {
|
|
776
|
+
const rows = [];
|
|
777
|
+
const calls = [];
|
|
778
|
+
let text = '';
|
|
779
|
+
let reasoning = '';
|
|
780
|
+
let tools = [];
|
|
781
|
+
let at = 0;
|
|
782
|
+
const flush = () => {
|
|
783
|
+
const row = buildPartialMessage(text, reasoning, tools, at);
|
|
784
|
+
if (row)
|
|
785
|
+
rows.push({ ...row, _turn_id: turnId });
|
|
786
|
+
text = '';
|
|
787
|
+
reasoning = '';
|
|
788
|
+
tools = [];
|
|
789
|
+
};
|
|
790
|
+
for (const e of events) {
|
|
791
|
+
const p = isDict(e.payload) ? e.payload : {};
|
|
792
|
+
if ((e.event === 'token' || e.event === 'reasoning') && tools.length)
|
|
793
|
+
flush();
|
|
794
|
+
if (e.event === 'token')
|
|
795
|
+
text += str(p.text);
|
|
796
|
+
else if (e.event === 'reasoning')
|
|
797
|
+
reasoning += str(p.text);
|
|
798
|
+
else if (e.event === 'tool') {
|
|
799
|
+
const call = { name: p.name, args: p.args ?? {}, tid: str(p.id), done: false };
|
|
800
|
+
calls.push(call);
|
|
801
|
+
tools.push(call);
|
|
802
|
+
}
|
|
803
|
+
else if (e.event === 'tool_complete') {
|
|
804
|
+
// The record is shared with the row that holds it, so a completion after its row closed still lands.
|
|
805
|
+
const call = calls[completedToolIndex(calls, str(p.id), p.name)];
|
|
806
|
+
if (call)
|
|
807
|
+
Object.assign(call, { done: true, snippet: p.preview, is_error: p.is_error === true, duration: p.duration ?? null, ...(isDict(p.result_view) ? { result_view: p.result_view } : {}), ...(isDict(p.edit_diff) ? { edit_diff: p.edit_diff } : {}) });
|
|
808
|
+
}
|
|
809
|
+
else
|
|
810
|
+
continue;
|
|
811
|
+
at = e.created_at || at;
|
|
812
|
+
}
|
|
813
|
+
flush();
|
|
814
|
+
const done = RunJournal.selectAuthoritativeTerminalEvent(events);
|
|
815
|
+
if (done?.event !== 'done')
|
|
816
|
+
return { rows, answered: false };
|
|
817
|
+
// The last row answers only with visible prose and no calls after it (a reasoning-only segment is no answer).
|
|
818
|
+
const last = rows[rows.length - 1];
|
|
819
|
+
if (last && !last._partial_tool_calls && str(last.content)) {
|
|
820
|
+
delete last._partial;
|
|
821
|
+
return { rows, answered: true };
|
|
822
|
+
}
|
|
823
|
+
const settled = isDict(done.payload) && isDict(done.payload.session) && Array.isArray(done.payload.session.messages) ? done.payload.session.messages : [];
|
|
824
|
+
const answer = settled.findLast((m) => isDict(m) && m.role === 'assistant' && m._turn_id === turnId);
|
|
825
|
+
// Its visible prose, from string or structured content, as the scene's final answer reads it.
|
|
826
|
+
const content = answer && !answer._error && !(Array.isArray(answer.tool_calls) && answer.tool_calls.length) ? splitDisplayText(messageText(answer.content))[0] : '';
|
|
827
|
+
if (!content)
|
|
828
|
+
return { rows, answered: false };
|
|
829
|
+
rows.push({ role: 'assistant', content, timestamp: Math.trunc(done.created_at), _turn_id: turnId });
|
|
830
|
+
return { rows, answered: true };
|
|
831
|
+
}
|
|
832
|
+
const DSML = '(?:\\s*|\\s*DSML\\s*[||]\\s*)?';
|
|
833
|
+
/** Markdown code (fenced blocks, an unclosed one running to the end, and inline spans): markup written there is literal. */
|
|
834
|
+
function codeRanges(text) {
|
|
835
|
+
const code = /^ {0,3}(`{3,}|~{3,})[^\n]*(?:\n[\s\S]*?(?:\n {0,3}\1[^\n]*(?=\n|$)|(?![\s\S]))|(?![\s\S]))|(`+)(?!`)(?:[^\n]|\n(?!\n))*?(?<!`)\2(?!`)/gm;
|
|
836
|
+
return [...text.matchAll(code)].map((m) => [m.index, m.index + m[0].length]);
|
|
837
|
+
}
|
|
838
|
+
const inCode = (code, at) => code.some(([from, to]) => at >= from && at < to);
|
|
839
|
+
/** `re` matches removed, except the ones that start inside Markdown code. */
|
|
840
|
+
function removeOutsideCode(text, re) {
|
|
841
|
+
const code = codeRanges(text);
|
|
842
|
+
return text.replace(re, (match, ...rest) => (inCode(code, rest.find((x) => typeof x === 'number') ?? 0) ? match : ''));
|
|
558
843
|
}
|
|
559
|
-
/**
|
|
560
|
-
export function
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
return [
|
|
844
|
+
/** Remove provider tool-call XML (`<function_calls>…`, DSML variants, `<tool_call>`), closed or cut off, that leaks into text. */
|
|
845
|
+
export function stripToolCallXml(text) {
|
|
846
|
+
const lo = text.toLowerCase();
|
|
847
|
+
if (!lo.includes('function_calls') && !lo.includes('dsml') && !lo.includes('<tool_call'))
|
|
848
|
+
return text;
|
|
849
|
+
return [
|
|
850
|
+
new RegExp(`<${DSML}function_calls>[\\s\\S]*?<\\/${DSML}function_calls>`, 'gi'),
|
|
851
|
+
new RegExp(`<${DSML}function_calls(?:>|$)[\\s\\S]*$`, 'i'),
|
|
852
|
+
/<tool_call>[\s\S]*?<\/tool_call>/gi,
|
|
853
|
+
/<tool_call>[\s\S]*$/i,
|
|
854
|
+
/<\s*|\s*DSML\s*[||]\s*/gi,
|
|
855
|
+
].reduce(removeOutsideCode, text).replace(/^\s+/, '').trimEnd();
|
|
856
|
+
}
|
|
857
|
+
/** Inline thinking openers: `<think>`/`<thinking>` in any case and with attributes (as the Agent's own split), and the channel forms. */
|
|
858
|
+
const THINK_OPEN = /<think(?:ing)?(?:\s[^>]*)?>|<\|channel\|>thought|<\|turn\|>thinking/gi;
|
|
859
|
+
function thinkClose(open) {
|
|
860
|
+
if (open.toLowerCase().startsWith('<|channel|>'))
|
|
861
|
+
return /<channel\|>/gi;
|
|
862
|
+
if (open.toLowerCase().startsWith('<|turn|>'))
|
|
863
|
+
return /<turn\|>/gi;
|
|
864
|
+
return /<\/think(?:ing)?\s*>/gi;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Split assistant text into its visible prose and its inline thinking: every `<think>`, `<thinking>`,
|
|
868
|
+
* `<|channel|>thought` and `<|turn|>thinking` block anywhere in the text, an unterminated one running to the end.
|
|
869
|
+
* A tag written inside Markdown code is prose. Tool-call XML leaves both halves; both come back trimmed.
|
|
870
|
+
*/
|
|
871
|
+
export function splitDisplayText(text) {
|
|
872
|
+
const code = codeRanges(text);
|
|
873
|
+
let content = '';
|
|
874
|
+
const reasoning = [];
|
|
875
|
+
let cursor = 0;
|
|
876
|
+
while (cursor < text.length) {
|
|
877
|
+
THINK_OPEN.lastIndex = cursor;
|
|
878
|
+
let open = THINK_OPEN.exec(text);
|
|
879
|
+
while (open && inCode(code, open.index))
|
|
880
|
+
open = THINK_OPEN.exec(text);
|
|
881
|
+
if (!open) {
|
|
882
|
+
content += text.slice(cursor);
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
content += text.slice(cursor, open.index);
|
|
886
|
+
const body = open.index + open[0].length;
|
|
887
|
+
const closer = thinkClose(open[0]);
|
|
888
|
+
closer.lastIndex = body;
|
|
889
|
+
const close = closer.exec(text);
|
|
890
|
+
reasoning.push(text.slice(body, close?.index));
|
|
891
|
+
if (!close)
|
|
892
|
+
break;
|
|
893
|
+
cursor = close.index + close[0].length;
|
|
894
|
+
}
|
|
895
|
+
return [stripToolCallXml(content).replace(/^\s+/, '').trimEnd(), joinReasoning(reasoning)];
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Reasoning parts as one display string: tool-call XML removed, blank-line joined, each part once. A part that is
|
|
899
|
+
* whole paragraphs of another (a settled row's `reasoning` that already took in its `reasoning_content`) is dropped.
|
|
900
|
+
*/
|
|
901
|
+
export function joinReasoning(parts) {
|
|
902
|
+
const clean = parts.map((part) => stripToolCallXml(part).trim()).filter(Boolean);
|
|
903
|
+
const within = (outer, inner) => `\n\n${outer}\n\n`.includes(`\n\n${inner}\n\n`);
|
|
904
|
+
return clean.filter((p, i) => !clean.some((q, j) => j !== i && (q === p ? j < i : within(q, p)))).join('\n\n');
|
|
905
|
+
}
|
|
906
|
+
/** A message's reasoning fields as one string: `reasoning_content`, `reasoning` (text or a list of text parts), `thinking`. */
|
|
907
|
+
export function reasoningFieldsText(m) {
|
|
908
|
+
const listed = Array.isArray(m.reasoning) ? m.reasoning.map((part) => (isDict(part) ? reasoningBlockText(part) : str(part))) : [str(m.reasoning)];
|
|
909
|
+
return joinReasoning([str(m.reasoning_content), ...listed, str(m.thinking)]);
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* The one display shape of an assistant row: `content` without inline thinking or tool-call XML (typed `thinking` /
|
|
913
|
+
* `reasoning` parts leave a list), and every piece of reasoning in one `reasoning` string. `reasoning_content` and
|
|
914
|
+
* `thinking` are not sent. Idempotent; other rows pass through. A copy: stored rows and model history are untouched.
|
|
915
|
+
*/
|
|
916
|
+
export function normalizeAssistantDisplay(message) {
|
|
917
|
+
if (!isDict(message) || message.role !== 'assistant')
|
|
918
|
+
return message;
|
|
919
|
+
const m = { ...message };
|
|
920
|
+
delete m.reasoning_content;
|
|
921
|
+
delete m.thinking;
|
|
922
|
+
delete m.reasoning;
|
|
923
|
+
const parts = [reasoningFieldsText(message)];
|
|
924
|
+
if (typeof m.content === 'string') {
|
|
925
|
+
const [content, inline] = splitDisplayText(m.content);
|
|
926
|
+
// Only text something was taken out of is rewritten: plain prose keeps its exact whitespace (an indented code block).
|
|
927
|
+
if (content !== m.content.trim())
|
|
928
|
+
m.content = content;
|
|
929
|
+
parts.push(inline);
|
|
930
|
+
}
|
|
931
|
+
else if (Array.isArray(m.content)) {
|
|
932
|
+
m.content = m.content.flatMap((part) => {
|
|
933
|
+
if (!isDict(part))
|
|
934
|
+
return [part];
|
|
935
|
+
if (isReasoningBlock(part)) {
|
|
936
|
+
parts.push(reasoningBlockText(part));
|
|
937
|
+
return [];
|
|
938
|
+
}
|
|
939
|
+
if (typeof part.text !== 'string')
|
|
940
|
+
return [part];
|
|
941
|
+
const [text, inline] = splitDisplayText(part.text);
|
|
942
|
+
parts.push(inline);
|
|
943
|
+
return text === part.text.trim() ? [part] : text ? [{ ...part, text }] : [];
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
const reasoning = joinReasoning(parts);
|
|
947
|
+
return (reasoning ? { ...m, reasoning } : m);
|
|
565
948
|
}
|
|
566
949
|
/**
|
|
567
950
|
* Python `merge_session_messages_append_only`, bounded to its load-bearing rules: state.db rows never delete a local
|
|
568
951
|
* row; rows that replay the sidecar (same role, timestamp, and content) are skipped; rows at or before the sidecar's
|
|
569
952
|
* newest timestamp are already represented locally; a truncation watermark hides rows the user cut (0 blocks every
|
|
570
|
-
* replay)
|
|
953
|
+
* replay): rows at or before it, and, for a truncation cut, rows after it until the sidecar advances past it (TAL-504).
|
|
954
|
+
* A compression watermark only covers the compressed rows. Rows past the sidecar tail (a conversation continued from the
|
|
955
|
+
* CLI) are appended in state.db order.
|
|
956
|
+
* TAL-493: once a boundary or settled turn recorded the highest state.db id it read (`stateDbSeenId`) and the rows carry
|
|
957
|
+
* ids, identity replaces the timestamp rules: rows up to that id are covered, and every newer row not already in the
|
|
958
|
+
* sidecar is appended, even one a writer stamped before the newest local row or the watermark but committed later.
|
|
571
959
|
* ponytail: the Python identity memo (api_content sidecars, message ids, workspace-prefix normalisation) is not ported;
|
|
572
960
|
* add it if a mixed WebUI/CLI transcript shows duplicated turns.
|
|
573
961
|
*/
|
|
@@ -578,6 +966,31 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
|
|
|
578
966
|
return sidecar;
|
|
579
967
|
const ts = (m) => { const n = Number(m.timestamp); return Number.isFinite(n) ? n : null; };
|
|
580
968
|
const key = (m) => `${String(m.role)}\0${String(ts(m) ?? '')}\0${typeof m.content === 'string' ? m.content : JSON.stringify(m.content ?? null)}`;
|
|
969
|
+
const seenId = opts.stateDbSeenId;
|
|
970
|
+
if (typeof seenId === 'number' && Number.isFinite(seenId) && state.some((m) => stateDbRowId(m) !== null)) {
|
|
971
|
+
// Each local row stands for at most one new state.db row: a persisted copy by its id, any other by its key.
|
|
972
|
+
const localIds = new Set(sidecar.map(stateDbRowId));
|
|
973
|
+
const localKeys = new Map();
|
|
974
|
+
for (const m of sidecar)
|
|
975
|
+
if (stateDbRowId(m) === null)
|
|
976
|
+
localKeys.set(key(m), (localKeys.get(key(m)) ?? 0) + 1);
|
|
977
|
+
const merged = [...sidecar];
|
|
978
|
+
for (const m of state) {
|
|
979
|
+
const id = stateDbRowId(m);
|
|
980
|
+
if (id !== null && localIds.has(id))
|
|
981
|
+
continue;
|
|
982
|
+
// In state.db order, so a covered row matches its own local copy before a newer duplicate can.
|
|
983
|
+
const left = localKeys.get(key(m)) ?? 0;
|
|
984
|
+
if (left > 0) {
|
|
985
|
+
localKeys.set(key(m), left - 1);
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (id === null || id <= seenId)
|
|
989
|
+
continue;
|
|
990
|
+
merged.push(m);
|
|
991
|
+
}
|
|
992
|
+
return merged;
|
|
993
|
+
}
|
|
581
994
|
if (!sidecar.length) {
|
|
582
995
|
if (!hasWatermark) {
|
|
583
996
|
const seen = new Set();
|
|
@@ -586,7 +999,7 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
|
|
|
586
999
|
}
|
|
587
1000
|
if (watermark === 0)
|
|
588
1001
|
return [];
|
|
589
|
-
return state.filter((m) => { const t = ts(m); return t !== null && t > watermark; });
|
|
1002
|
+
return state.filter((m) => { const t = ts(m); return t !== null && (opts.compressedWatermark === true ? t > watermark : t <= watermark); });
|
|
590
1003
|
}
|
|
591
1004
|
const seen = new Set(sidecar.map(key));
|
|
592
1005
|
let maxSidecar = null;
|
|
@@ -595,6 +1008,8 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
|
|
|
595
1008
|
if (t !== null && (maxSidecar === null || t > maxSidecar))
|
|
596
1009
|
maxSidecar = t;
|
|
597
1010
|
}
|
|
1011
|
+
// Until a new turn lands in the sidecar, every state.db row past the watermark is the deleted suffix.
|
|
1012
|
+
const advanced = !hasWatermark || opts.compressedWatermark === true || (maxSidecar !== null && maxSidecar > watermark);
|
|
598
1013
|
const merged = [...sidecar];
|
|
599
1014
|
for (const m of state) {
|
|
600
1015
|
const k = key(m);
|
|
@@ -603,13 +1018,28 @@ export function mergeSessionMessagesAppendOnly(sidecar, state, opts = {}) {
|
|
|
603
1018
|
const t = ts(m);
|
|
604
1019
|
if (maxSidecar !== null && t !== null && t <= maxSidecar)
|
|
605
1020
|
continue;
|
|
606
|
-
if (hasWatermark && t !== null && t <= watermark)
|
|
1021
|
+
if (hasWatermark && t !== null && (t <= watermark || !advanced))
|
|
607
1022
|
continue;
|
|
608
1023
|
seen.add(k);
|
|
609
1024
|
merged.push(m);
|
|
610
1025
|
}
|
|
611
1026
|
return merged;
|
|
612
1027
|
}
|
|
1028
|
+
/** The Agent's `messages.id` a state.db row was read with, or null for a database without the column. */
|
|
1029
|
+
function stateDbRowId(m) {
|
|
1030
|
+
const id = m._state_db_row_id;
|
|
1031
|
+
return typeof id === 'number' && Number.isFinite(id) ? id : null;
|
|
1032
|
+
}
|
|
1033
|
+
/** TAL-493: the `state_db_seen_id` after reading `rows`: their highest id, or null when they carry none. */
|
|
1034
|
+
export function stateDbSeenId(rows) {
|
|
1035
|
+
let max = null;
|
|
1036
|
+
for (const m of rows) {
|
|
1037
|
+
const id = stateDbRowId(m);
|
|
1038
|
+
if (id !== null && (max === null || id > max))
|
|
1039
|
+
max = id;
|
|
1040
|
+
}
|
|
1041
|
+
return max;
|
|
1042
|
+
}
|
|
613
1043
|
/**
|
|
614
1044
|
* TAL-316: the merged transcript without the running turn's output, which the run journal replays from its start. The turn
|
|
615
1045
|
* begins at its checkpointed prompt, else at its first stamped row or the first state.db row appended past the sidecar
|
|
@@ -644,6 +1074,46 @@ export function attachmentObjects(items) {
|
|
|
644
1074
|
export function withAttachmentObjects(messages) {
|
|
645
1075
|
return messages.map((m) => (isDict(m) && Array.isArray(m.attachments) && m.attachments.some((a) => typeof a === 'string') ? { ...m, attachments: attachmentObjects(m.attachments) } : m));
|
|
646
1076
|
}
|
|
1077
|
+
/** A scene's prose rows with `display_text` and `media` where `project` rewrites their text; the same array when none does. */
|
|
1078
|
+
export function withSceneRowMedia(rows, project) {
|
|
1079
|
+
if (!Array.isArray(rows))
|
|
1080
|
+
return rows;
|
|
1081
|
+
let changed = false;
|
|
1082
|
+
const out = rows.map((row) => {
|
|
1083
|
+
if (!isDict(row) || row.role !== 'prose' || typeof row.text !== 'string')
|
|
1084
|
+
return row;
|
|
1085
|
+
const projected = project(row.text);
|
|
1086
|
+
if (!projected)
|
|
1087
|
+
return row;
|
|
1088
|
+
changed = true;
|
|
1089
|
+
return { ...row, display_text: projected.text, media: projected.media };
|
|
1090
|
+
});
|
|
1091
|
+
return changed ? out : rows;
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* TAL-186: assistant rows with `_display_content` and `_media`, and their scenes with `final_answer_display` /
|
|
1095
|
+
* `final_answer_media` and prose rows' `display_text` / `media`, wherever `project` rewrites media references in the
|
|
1096
|
+
* text. Runs after scene hydration and before body excerpts, which are cut from the display text. Returns copies.
|
|
1097
|
+
*/
|
|
1098
|
+
export function withDisplayMedia(messages, project) {
|
|
1099
|
+
return messages.map((m) => {
|
|
1100
|
+
if (!isDict(m) || m.role !== 'assistant')
|
|
1101
|
+
return m;
|
|
1102
|
+
let out = m;
|
|
1103
|
+
const projected = project(messageText(m.content));
|
|
1104
|
+
if (projected)
|
|
1105
|
+
out = { ...out, _display_content: projected.text, _media: projected.media };
|
|
1106
|
+
const scene = m._anchor_activity_scene;
|
|
1107
|
+
if (isDict(scene)) {
|
|
1108
|
+
const final = typeof scene.final_answer === 'string' ? project(scene.final_answer) : null;
|
|
1109
|
+
const rows = withSceneRowMedia(scene.activity_rows, project);
|
|
1110
|
+
if (final || rows !== scene.activity_rows) {
|
|
1111
|
+
out = { ...out, _anchor_activity_scene: { ...scene, activity_rows: rows, ...(final ? { final_answer_display: final.text, final_answer_media: final.media } : {}) } };
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return out;
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
647
1117
|
/** TAL-456: a settled user or assistant body longer than this ships a collapsed excerpt for clients to render. */
|
|
648
1118
|
export const BODY_EXCERPT_LIMIT = 3000;
|
|
649
1119
|
const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
@@ -663,7 +1133,7 @@ function bodyExcerpt(text) {
|
|
|
663
1133
|
return head.slice(0, cut).trimEnd();
|
|
664
1134
|
}
|
|
665
1135
|
/**
|
|
666
|
-
* TAL-456: stamps `_display_excerpt` and `_display_truncated` on settled user and assistant rows whose text is longer than
|
|
1136
|
+
* TAL-456: stamps `_display_excerpt` and `_display_truncated` on settled user and assistant rows whose display text is longer than
|
|
667
1137
|
* `BODY_EXCERPT_LIMIT`, and `final_answer_excerpt` on a settled scene whose final answer is, so clients render a bounded
|
|
668
1138
|
* excerpt instead of laying out the whole body. Full text stays for copy and edit; the running turn's rows are left alone.
|
|
669
1139
|
* Runs after scene hydration. Returns copies; stored rows are untouched.
|
|
@@ -675,12 +1145,13 @@ export function withBodyExcerpts(messages, activeTurnId) {
|
|
|
675
1145
|
if (activeTurnId && m._turn_id === activeTurnId)
|
|
676
1146
|
return m;
|
|
677
1147
|
let out = m;
|
|
678
|
-
const text = messageText(m.content);
|
|
1148
|
+
const text = typeof m._display_content === 'string' ? m._display_content : messageText(m.content);
|
|
679
1149
|
if (text.length > BODY_EXCERPT_LIMIT)
|
|
680
1150
|
out = { ...out, _display_excerpt: bodyExcerpt(text), _display_truncated: true };
|
|
681
1151
|
const scene = m._anchor_activity_scene;
|
|
682
|
-
|
|
683
|
-
|
|
1152
|
+
const finalAnswer = isDict(scene) ? (typeof scene.final_answer_display === 'string' ? scene.final_answer_display : scene.final_answer) : null;
|
|
1153
|
+
if (isDict(scene) && typeof finalAnswer === 'string' && finalAnswer.length > BODY_EXCERPT_LIMIT) {
|
|
1154
|
+
out = { ...out, _anchor_activity_scene: { ...scene, final_answer_excerpt: bodyExcerpt(finalAnswer) } };
|
|
684
1155
|
}
|
|
685
1156
|
return out;
|
|
686
1157
|
});
|
|
@@ -758,7 +1229,8 @@ export function stoppedTurnContext(previousContext, checkpoint, prompt, msgText,
|
|
|
758
1229
|
// A tool result is a `tool` row or an Anthropic-style user row of `tool_result` blocks. A steer the Agent applied is
|
|
759
1230
|
// neither a prompt nor a result: the boundary goes after it, and it never makes the turn look unanswered.
|
|
760
1231
|
const toolResult = (m) => m?.role === 'tool' || (m?.role === 'user' && Array.isArray(m.content) && m.content.some((part) => isDict(part) && part.type === 'tool_result'));
|
|
761
|
-
|
|
1232
|
+
// The next turn's own history projection, so an answer the Agent replays from `api_content` counts.
|
|
1233
|
+
const tail = () => sanitizeMessagesForApi(rows.filter((m) => agentSteerText(m) === null), { preserveApiContent: true }).at(-1);
|
|
762
1234
|
if (toolResult(tail()))
|
|
763
1235
|
rows.push({ role: 'assistant', content: unsettled || 'Operation interrupted.' });
|
|
764
1236
|
else if (unsettled)
|
|
@@ -783,6 +1255,10 @@ export function agentSteerText(m) {
|
|
|
783
1255
|
return inner.trim();
|
|
784
1256
|
return m.display_kind === 'steer' ? content.trim() : null;
|
|
785
1257
|
}
|
|
1258
|
+
/** The Agent's `api_content` replay sidecar: the exact bytes a user/assistant row was sent as, else undefined. */
|
|
1259
|
+
function apiContent(msg) {
|
|
1260
|
+
return (msg.role === 'user' || msg.role === 'assistant') && typeof msg.api_content === 'string' && msg.api_content ? msg.api_content : undefined;
|
|
1261
|
+
}
|
|
786
1262
|
/** Python `_is_reasoning_only_assistant_message`: a display-only Thinking card with no visible reply. */
|
|
787
1263
|
function isReasoningOnlyAssistant(msg) {
|
|
788
1264
|
if (msg.role !== 'assistant' || (Array.isArray(msg.tool_calls) && msg.tool_calls.length))
|
|
@@ -807,8 +1283,10 @@ function stripOobBlocks(content) {
|
|
|
807
1283
|
* reasoning-only assistants), orphaned tool rows and unanswered tool calls, keeps only API-safe keys, strips consumed
|
|
808
1284
|
* out-of-band blocks, and keeps a cancelled (`_recovered`) user prompt only where it separates two assistant turns —
|
|
809
1285
|
* otherwise the neighbours fuse cleanly or the prompt is stale, and replaying it would answer it again.
|
|
1286
|
+
* `preserveApiContent` (Python `_sanitize_messages_for_agent`) keeps the Agent's `api_content` replay sidecar on
|
|
1287
|
+
* user/assistant rows for turn history; compression and other projections strip it.
|
|
810
1288
|
*/
|
|
811
|
-
export function sanitizeMessagesForApi(messages) {
|
|
1289
|
+
export function sanitizeMessagesForApi(messages, { preserveApiContent = false } = {}) {
|
|
812
1290
|
// Calls are OpenAI `tool_calls` or Anthropic-style `tool_use` content blocks; results name them by `tool_call_id`,
|
|
813
1291
|
// `tool_use_id`, or a user row's `tool_result` blocks.
|
|
814
1292
|
const toolUseIds = (msg) => (Array.isArray(msg.content) ? msg.content.flatMap((part) => (isDict(part) && part.type === 'tool_use' && str(part.id) ? [str(part.id)] : [])) : []);
|
|
@@ -828,7 +1306,9 @@ export function sanitizeMessagesForApi(messages) {
|
|
|
828
1306
|
for (const msg of messages) {
|
|
829
1307
|
if (!msg || typeof msg !== 'object')
|
|
830
1308
|
continue;
|
|
831
|
-
|
|
1309
|
+
// A reasoning-only clean stop the Agent answered from `api_content` is a reply, not a Thinking card.
|
|
1310
|
+
const replay = preserveApiContent ? apiContent(msg) : undefined;
|
|
1311
|
+
if (isReasoningOnlyAssistant(msg) && !replay)
|
|
832
1312
|
continue;
|
|
833
1313
|
// The Agent already received a steer mid-turn; its persisted row is display-only.
|
|
834
1314
|
if (msg._error || isDict(msg._steer))
|
|
@@ -844,6 +1324,8 @@ export function sanitizeMessagesForApi(messages) {
|
|
|
844
1324
|
const sanitized = Object.fromEntries(Object.entries(msg).filter(([k]) => API_SAFE_MSG_KEYS.has(k)));
|
|
845
1325
|
if (Array.isArray(sanitized.tool_calls) && !sanitized.tool_calls.length)
|
|
846
1326
|
Reflect.deleteProperty(sanitized, 'tool_calls');
|
|
1327
|
+
if (replay)
|
|
1328
|
+
sanitized.api_content = replay;
|
|
847
1329
|
if (recovered)
|
|
848
1330
|
sanitized._recovered = true;
|
|
849
1331
|
if ('content' in sanitized)
|
|
@@ -866,7 +1348,7 @@ export function sanitizeMessagesForApi(messages) {
|
|
|
866
1348
|
continue;
|
|
867
1349
|
msg = { ...msg, content: parts };
|
|
868
1350
|
// Reasoning left behind by a dropped call is not an answer either.
|
|
869
|
-
if (isReasoningOnlyAssistant(msg))
|
|
1351
|
+
if (isReasoningOnlyAssistant(msg) && !apiContent(msg))
|
|
870
1352
|
continue;
|
|
871
1353
|
}
|
|
872
1354
|
if (msg.role === 'assistant' && Array.isArray(msg.tool_calls) && msg.tool_calls.length) {
|
|
@@ -895,4 +1377,33 @@ export function sanitizeMessagesForApi(messages) {
|
|
|
895
1377
|
}
|
|
896
1378
|
return final;
|
|
897
1379
|
}
|
|
1380
|
+
const isNativeImagePart = (part) => isDict(part) && (part.type === 'image_url' || 'image_url' in part);
|
|
1381
|
+
/** True when any row's content carries a native `image_url` part. */
|
|
1382
|
+
export function hasNativeImages(messages) {
|
|
1383
|
+
return messages.some((msg) => Array.isArray(msg.content) && msg.content.some(isNativeImagePart));
|
|
1384
|
+
}
|
|
1385
|
+
/**
|
|
1386
|
+
* Python `_strip_native_image_parts_from_content` (TAL-545): the history for a turn whose image mode resolved to text.
|
|
1387
|
+
* Native `image_url` parts are dropped and the text kept; a lone text part collapses to its string, as a text-only
|
|
1388
|
+
* provider rejects a replayed image before the Agent's own strip-and-retry guard can recover.
|
|
1389
|
+
*/
|
|
1390
|
+
export function withoutNativeImages(messages) {
|
|
1391
|
+
return messages.map((msg) => {
|
|
1392
|
+
if (!Array.isArray(msg.content) || !msg.content.some(isNativeImagePart))
|
|
1393
|
+
return msg;
|
|
1394
|
+
const parts = msg.content.filter((part) => isDict(part) && !isNativeImagePart(part));
|
|
1395
|
+
const content = !parts.length ? '' : parts.length === 1 && parts[0].type === 'text' ? str(parts[0].text) : parts;
|
|
1396
|
+
return { ...msg, content };
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* TAL-545: the image-free history is one request's projection, never the session's. The Agent hands the history it got
|
|
1401
|
+
* back ahead of its own rows, so each row it returned exactly as sent (`sent[i]`, stripped from `original[i]`) is swapped
|
|
1402
|
+
* back for the original; a row the Agent rewrote stays its own.
|
|
1403
|
+
*/
|
|
1404
|
+
export function withNativeImagesRestored(rows, sent, original) {
|
|
1405
|
+
if (sent === original)
|
|
1406
|
+
return rows;
|
|
1407
|
+
return rows.map((row, i) => (i < sent.length && sent[i] !== original[i] && JSON.stringify(row) === JSON.stringify(sent[i]) ? original[i] : row));
|
|
1408
|
+
}
|
|
898
1409
|
//# sourceMappingURL=merge.js.map
|