@hienlh/ppm 0.23.8 → 0.23.10
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/CHANGELOG.md +16 -0
- package/CLAUDE.md +4 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/accounts-settings-section-BOBJbG7i.js +1 -0
- package/dist/web/assets/accounts-settings-section-BOBJbG7i.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-BOBJbG7i.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-CjD4hBZV.js +2 -0
- package/dist/web/assets/agent-session-sheet-CjD4hBZV.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-CjD4hBZV.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-store-BcQbnaV2.js +1 -0
- package/dist/web/assets/agent-session-window-content-Drm1HsSn.js +1 -0
- package/dist/web/assets/agent-session-window-content-Drm1HsSn.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-Drm1HsSn.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-DfVRMUHS.js → ai-resource-editor-M1Eax2Hc.js} +1 -1
- package/dist/web/assets/ai-resource-editor-M1Eax2Hc.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-M1Eax2Hc.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-B73t9SXm.js +1 -0
- package/dist/web/assets/{ai-settings-section-Ezhw4Fef.js → ai-settings-section-H173dm1k.js} +1 -1
- package/dist/web/assets/ai-settings-section-H173dm1k.js.br +0 -0
- package/dist/web/assets/ai-settings-section-H173dm1k.js.gz +0 -0
- package/dist/web/assets/{api-settings-tYKJ5Fqk.js → api-settings-BA_n8cPt.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-CPOAp0fY.js → appearance-settings-section-Dqu20pt8.js} +1 -1
- package/dist/web/assets/appearance-settings-section-Dqu20pt8.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-Dqu20pt8.js.gz +0 -0
- package/dist/web/assets/{apps-panel-Cz7_o-M4.js → apps-panel-mkrzY7K0.js} +1 -1
- package/dist/web/assets/apps-panel-mkrzY7K0.js.br +0 -0
- package/dist/web/assets/apps-panel-mkrzY7K0.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-Dw3bNixU.js +1 -0
- package/dist/web/assets/branch-review-tab-Dw3bNixU.js.br +0 -0
- package/dist/web/assets/branch-review-tab-Dw3bNixU.js.gz +0 -0
- package/dist/web/assets/chat-tab-BAbcVFzD.js +15 -0
- package/dist/web/assets/chat-tab-BAbcVFzD.js.br +0 -0
- package/dist/web/assets/chat-tab-BAbcVFzD.js.gz +0 -0
- package/dist/web/assets/chat-tab-BaXGaBEC.js +1 -0
- package/dist/web/assets/chat-tab-BaXGaBEC.js.br +4 -0
- package/dist/web/assets/chat-tab-BaXGaBEC.js.gz +0 -0
- package/dist/web/assets/code-editor-CXLANXnI.js +7 -0
- package/dist/web/assets/code-editor-CXLANXnI.js.br +0 -0
- package/dist/web/assets/code-editor-CXLANXnI.js.gz +0 -0
- package/dist/web/assets/{codex-reset-credit-button-D2hal4bG.js → codex-reset-credit-button-CB8Dfhki.js} +1 -1
- package/dist/web/assets/codex-reset-credit-button-CB8Dfhki.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-CB8Dfhki.js.gz +0 -0
- package/dist/web/assets/{conflict-editor-Cj_gUncw.js → conflict-editor-CKsxIw3v.js} +2 -2
- package/dist/web/assets/conflict-editor-CKsxIw3v.js.br +0 -0
- package/dist/web/assets/conflict-editor-CKsxIw3v.js.gz +0 -0
- package/dist/web/assets/{csv-preview-Be3ODNj0.js → csv-preview-U0v_r_-7.js} +1 -1
- package/dist/web/assets/csv-preview-U0v_r_-7.js.br +0 -0
- package/dist/web/assets/csv-preview-U0v_r_-7.js.gz +0 -0
- package/dist/web/assets/{database-viewer-B1b--4y8.js → database-viewer-xQyUcBoL.js} +1 -1
- package/dist/web/assets/database-viewer-xQyUcBoL.js.br +0 -0
- package/dist/web/assets/database-viewer-xQyUcBoL.js.gz +0 -0
- package/dist/web/assets/{design-tab-8wi0IHss.js → design-tab-aaAt2_QE.js} +4 -4
- package/dist/web/assets/design-tab-aaAt2_QE.js.br +0 -0
- package/dist/web/assets/design-tab-aaAt2_QE.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-D5-1Q8zQ.js → designs-sidebar-panel-CTzO4Thu.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-CTzO4Thu.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-CTzO4Thu.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-Dcxk6UTG.js → diff-viewer-CSE2-OE7.js} +1 -1
- package/dist/web/assets/diff-viewer-CSE2-OE7.js.br +0 -0
- package/dist/web/assets/diff-viewer-CSE2-OE7.js.gz +0 -0
- package/dist/web/assets/{edit-diff-preview-DxDAtYhi.js → edit-diff-preview-gH6pRk4S.js} +1 -1
- package/dist/web/assets/edit-diff-preview-gH6pRk4S.js.br +0 -0
- package/dist/web/assets/{edit-diff-preview-DxDAtYhi.js.gz → edit-diff-preview-gH6pRk4S.js.gz} +0 -0
- package/dist/web/assets/{editor-language-service-BVJQnTSV.js → editor-language-service-CYl04pUy.js} +1 -1
- package/dist/web/assets/editor-language-service-CYl04pUy.js.br +0 -0
- package/dist/web/assets/editor-language-service-CYl04pUy.js.gz +0 -0
- package/dist/web/assets/explorer-body-BBOcvwmV.js +2 -0
- package/dist/web/assets/explorer-body-BBOcvwmV.js.br +0 -0
- package/dist/web/assets/explorer-body-BBOcvwmV.js.gz +0 -0
- package/dist/web/assets/{explorer-window-content-DH5AD3Ng.js → explorer-window-content-juCGcrkc.js} +1 -1
- package/dist/web/assets/explorer-window-content-juCGcrkc.js.br +0 -0
- package/dist/web/assets/explorer-window-content-juCGcrkc.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-BJr6ccWw.js → extension-manager-section-poMRmwvg.js} +1 -1
- package/dist/web/assets/extension-manager-section-poMRmwvg.js.br +0 -0
- package/dist/web/assets/extension-manager-section-poMRmwvg.js.gz +0 -0
- package/dist/web/assets/{extension-webview-CYNXHd8D.js → extension-webview-0QfzTI6s.js} +1 -1
- package/dist/web/assets/extension-webview-0QfzTI6s.js.br +0 -0
- package/dist/web/assets/extension-webview-0QfzTI6s.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-JX7o4lex.js → files-settings-section-DJptxRgH.js} +1 -1
- package/dist/web/assets/files-settings-section-DJptxRgH.js.br +0 -0
- package/dist/web/assets/files-settings-section-DJptxRgH.js.gz +0 -0
- package/dist/web/assets/{general-settings-section-kF94Mz5q.js → general-settings-section-BFNbdDNv.js} +1 -1
- package/dist/web/assets/general-settings-section-BFNbdDNv.js.br +0 -0
- package/dist/web/assets/general-settings-section-BFNbdDNv.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-Crlp9ofM.js → git-log-panel-SMMIbjSe.js} +1 -1
- package/dist/web/assets/git-log-panel-SMMIbjSe.js.br +0 -0
- package/dist/web/assets/git-log-panel-SMMIbjSe.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-O6MO5dKU.js → glide-data-grid-DZVmgu0T.js} +3 -3
- package/dist/web/assets/glide-data-grid-DZVmgu0T.js.br +0 -0
- package/dist/web/assets/glide-data-grid-DZVmgu0T.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-d_aaYuK_.js +1 -0
- package/dist/web/assets/group-chat-tab-d_aaYuK_.js.br +0 -0
- package/dist/web/assets/group-chat-tab-d_aaYuK_.js.gz +0 -0
- package/dist/web/assets/{index-DQCw6d-2.js → index-CIscPw4g.js} +6 -6
- package/dist/web/assets/index-CIscPw4g.js.br +0 -0
- package/dist/web/assets/index-CIscPw4g.js.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-DwM7gqHT.js → jira-watcher-section-CsbhnLLn.js} +1 -1
- package/dist/web/assets/keybindings-store-BPs0pM0r.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-CewWGwiF.js → keyboard-shortcuts-section-CXqc2OtM.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-CXqc2OtM.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-CXqc2OtM.js.gz +0 -0
- package/dist/web/assets/{language-servers-section-nricdxke.js → language-servers-section-B4FsBVvZ.js} +2 -2
- package/dist/web/assets/language-servers-section-B4FsBVvZ.js.br +0 -0
- package/dist/web/assets/language-servers-section-B4FsBVvZ.js.gz +0 -0
- package/dist/web/assets/{lsp-client-DRMEWb-z.js → lsp-client-MQ4mjDfT.js} +1 -1
- package/dist/web/assets/lsp-client-MQ4mjDfT.js.br +0 -0
- package/dist/web/assets/lsp-client-MQ4mjDfT.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-BvCZZlue.js +1 -0
- package/dist/web/assets/{markdown-renderer-Dab9Us6u.js → markdown-renderer-CjPb5pns.js} +3 -3
- package/dist/web/assets/markdown-renderer-CjPb5pns.js.br +0 -0
- package/dist/web/assets/markdown-renderer-CjPb5pns.js.gz +0 -0
- package/dist/web/assets/{mobile-explorer-sheet-DtgdpvJ9.js → mobile-explorer-sheet-0aGhVQtI.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-0aGhVQtI.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-0aGhVQtI.js.gz +0 -0
- package/dist/web/assets/{new-design-dialog-Dd5KDB_p.js → new-design-dialog-2oT2oc_F.js} +1 -1
- package/dist/web/assets/new-design-dialog-2oT2oc_F.js.br +0 -0
- package/dist/web/assets/new-design-dialog-2oT2oc_F.js.gz +0 -0
- package/dist/web/assets/notification-store-Dd25PMPQ.js +1 -0
- package/dist/web/assets/{panel-store-DrSH3Nhp.js → panel-store-1X6yM3XZ.js} +1 -1
- package/dist/web/assets/panel-store-1X6yM3XZ.js.br +0 -0
- package/dist/web/assets/panel-store-1X6yM3XZ.js.gz +0 -0
- package/dist/web/assets/performance-panel-Ub96qrEH.js +3 -0
- package/dist/web/assets/performance-panel-Ub96qrEH.js.br +0 -0
- package/dist/web/assets/performance-panel-Ub96qrEH.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-D67u6gPi.js → ppmbot-settings-section-CPMGP66g.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-CPMGP66g.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-CPMGP66g.js.gz +0 -0
- package/dist/web/assets/{problems-panel-DKgIw4uZ.js → problems-panel-CWwzZIP3.js} +1 -1
- package/dist/web/assets/problems-panel-CWwzZIP3.js.br +0 -0
- package/dist/web/assets/problems-panel-CWwzZIP3.js.gz +0 -0
- package/dist/web/assets/process-table-BwPws3zS.js +1 -0
- package/dist/web/assets/process-table-BwPws3zS.js.br +0 -0
- package/dist/web/assets/process-table-BwPws3zS.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-CFiYcTBA.js → proxy-settings-section-nS8VbxI_.js} +1 -1
- package/dist/web/assets/proxy-settings-section-nS8VbxI_.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-nS8VbxI_.js.gz +0 -0
- package/dist/web/assets/{query-audit-section-BybgiPsc.js → query-audit-section-Dv5Pxn-w.js} +1 -1
- package/dist/web/assets/query-audit-section-Dv5Pxn-w.js.br +0 -0
- package/dist/web/assets/query-audit-section-Dv5Pxn-w.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-clipboard-notice-Bs3fZSRw.js → remote-desktop-clipboard-notice-DEbdu0hC.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-DEbdu0hC.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-DEbdu0hC.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BJm-akVY.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BJm-akVY.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BJm-akVY.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-mobile-view-DgGOfPVY.js → remote-desktop-mobile-view-DPdGFD6C.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-view-DPdGFD6C.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DPdGFD6C.js.gz +0 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-DOWryrQx.js → remote-desktop-readiness-gate-DV7YgSpP.js} +1 -1
- package/dist/web/assets/remote-desktop-readiness-gate-DV7YgSpP.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-DV7YgSpP.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-BktcWMjD.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-BktcWMjD.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-BktcWMjD.js.gz +0 -0
- package/dist/web/assets/{run-in-terminal-Cdn9PaAl.js → run-in-terminal-B_PCeBqG.js} +1 -1
- package/dist/web/assets/{schedules-settings-section-DGhG-b5O.js → schedules-settings-section-B3HYKSVh.js} +1 -1
- package/dist/web/assets/schedules-settings-section-B3HYKSVh.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-B3HYKSVh.js.gz +0 -0
- package/dist/web/assets/{send-to-chat-B09VjsB5.js → send-to-chat-Bjg6c8Ld.js} +1 -1
- package/dist/web/assets/send-to-chat-Bjg6c8Ld.js.br +0 -0
- package/dist/web/assets/send-to-chat-Bjg6c8Ld.js.gz +0 -0
- package/dist/web/assets/{services-panel-0klE3O4g.js → services-panel-D6k6AVS4.js} +1 -1
- package/dist/web/assets/services-panel-D6k6AVS4.js.br +0 -0
- package/dist/web/assets/services-panel-D6k6AVS4.js.gz +0 -0
- package/dist/web/assets/{settings-body-CfqpqFfq.js → settings-body-Cu0mNA5x.js} +2 -2
- package/dist/web/assets/settings-body-Cu0mNA5x.js.br +0 -0
- package/dist/web/assets/settings-body-Cu0mNA5x.js.gz +0 -0
- package/dist/web/assets/{settings-store-3jIBB8gx.js → settings-store-DIV25PM_.js} +2 -2
- package/dist/web/assets/settings-store-DIV25PM_.js.br +0 -0
- package/dist/web/assets/settings-store-DIV25PM_.js.gz +0 -0
- package/dist/web/assets/{settings-tab-CHweH5Nh.js → settings-tab-C-A6RNQ3.js} +1 -1
- package/dist/web/assets/{settings-window-content-C6Od3qV7.js → settings-window-content-48e5FJAq.js} +1 -1
- package/dist/web/assets/{sql-query-editor-ALQ5_X0j.js → sql-query-editor-CgGWYu9K.js} +1 -1
- package/dist/web/assets/sql-query-editor-CgGWYu9K.js.br +0 -0
- package/dist/web/assets/sql-query-editor-CgGWYu9K.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-DKTty_jR.js → sqlite-viewer-Co1Oi3dv.js} +1 -1
- package/dist/web/assets/sqlite-viewer-Co1Oi3dv.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-Co1Oi3dv.js.gz +0 -0
- package/dist/web/assets/{system-monitor-body-D6J6lq7-.js → system-monitor-body-DxPKVa-W.js} +2 -2
- package/dist/web/assets/system-monitor-body-DxPKVa-W.js.br +0 -0
- package/dist/web/assets/system-monitor-body-DxPKVa-W.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-4miOZuQQ.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-D1t30f32.js → system-monitor-window-content-B-A7DdML.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-DTcx2D0O.js → tab-host-window-content-CChcpg1v.js} +1 -1
- package/dist/web/assets/tab-host-window-content-CChcpg1v.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-CChcpg1v.js.gz +0 -0
- package/dist/web/assets/{tab-store-CUPgQIQz.js → tab-store-D33icgdA.js} +1 -1
- package/dist/web/assets/{terminal-tab-DgTfQKBI.js → terminal-tab-BDqlyBLK.js} +2 -2
- package/dist/web/assets/terminal-tab-BDqlyBLK.js.br +0 -0
- package/dist/web/assets/terminal-tab-BDqlyBLK.js.gz +0 -0
- package/dist/web/assets/tool-cards-iJ8tyrLq.js +9 -0
- package/dist/web/assets/tool-cards-iJ8tyrLq.js.br +0 -0
- package/dist/web/assets/tool-cards-iJ8tyrLq.js.gz +0 -0
- package/dist/web/assets/{use-inline-blame-Dv111BKY.js → use-inline-blame-D9js570a.js} +4 -4
- package/dist/web/assets/use-inline-blame-D9js570a.js.br +0 -0
- package/dist/web/assets/use-inline-blame-D9js570a.js.gz +0 -0
- package/dist/web/assets/{use-lsp-CZTFJIXX.js → use-lsp-BYtylZId.js} +1 -1
- package/dist/web/assets/use-lsp-BYtylZId.js.br +0 -0
- package/dist/web/assets/use-lsp-BYtylZId.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-DL2dIG5L.js → use-monaco-theme-DJ8y_7IZ.js} +1 -1
- package/dist/web/assets/{use-process-kill-D7D9QvJG.js → use-process-kill-BS84S-kD.js} +1 -1
- package/dist/web/assets/use-process-kill-BS84S-kD.js.br +0 -0
- package/dist/web/assets/use-process-kill-BS84S-kD.js.gz +0 -0
- package/dist/web/assets/{use-websocket-ziY-Gz_B.js → use-websocket-Mqj27umG.js} +1 -1
- package/dist/web/assets/{video-preview-DzwY3q9W.js → video-preview-BuzUAIma.js} +1 -1
- package/dist/web/assets/video-preview-BuzUAIma.js.br +0 -0
- package/dist/web/assets/video-preview-BuzUAIma.js.gz +0 -0
- package/dist/web/assets/{voice-settings-section-B7zMHsuz.js → voice-settings-section-DnwQjBbw.js} +1 -1
- package/dist/web/assets/voice-settings-section-DnwQjBbw.js.br +0 -0
- package/dist/web/assets/voice-settings-section-DnwQjBbw.js.gz +0 -0
- package/dist/web/assets/{window-pip-registry-dxrBFfkP.js → window-pip-registry-Bl--fK8-.js} +1 -1
- package/dist/web/assets/window-store-vo1N70nK.js +1 -0
- package/dist/web/assets/window-store-vo1N70nK.js.br +0 -0
- package/dist/web/assets/window-store-vo1N70nK.js.gz +0 -0
- package/dist/web/index.html +6 -6
- package/dist/web/index.html.br +0 -0
- package/dist/web/index.html.gz +0 -0
- package/dist/web/sw.js +1 -1
- package/dist/web/sw.js.br +0 -0
- package/dist/web/sw.js.gz +0 -0
- package/docs/architecture/ai-chat-and-providers.md +66 -0
- package/docs/architecture/workspace-and-ui.md +15 -3
- package/docs/design-guidelines.md +6 -1
- package/package.json +1 -1
- package/src/providers/codex-app-server/codex-history.ts +4 -4
- package/src/providers/codex-app-server/codex-provider.ts +1 -1
- package/src/providers/codex-app-server/codex-rollout-tail-parser.ts +203 -0
- package/src/providers/codex-app-server/codex-subagent-thread.ts +7 -1
- package/src/server/index.ts +7 -2
- package/src/server/routes/chat.ts +12 -4
- package/src/server/ws/global.ts +19 -3
- package/src/services/agent-transcript/agent-transcript-activity.ts +72 -0
- package/src/services/agent-transcript/agent-transcript-codex-activity.ts +161 -0
- package/src/services/agent-transcript/agent-transcript-codex-tail-cache.ts +85 -0
- package/src/services/agent-transcript/agent-transcript-envelope.ts +86 -0
- package/src/services/agent-transcript/agent-transcript-error-map.ts +33 -0
- package/src/services/agent-transcript/agent-transcript-file-tail-feed.ts +180 -0
- package/src/services/agent-transcript/agent-transcript-file-tail-state.ts +100 -0
- package/src/services/agent-transcript/agent-transcript-fs-io.ts +79 -0
- package/src/services/agent-transcript/agent-transcript-hub-clock.ts +37 -0
- package/src/services/agent-transcript/agent-transcript-hub-registry.ts +153 -0
- package/src/services/agent-transcript/agent-transcript-hub.ts +103 -0
- package/src/services/agent-transcript/agent-transcript-index-cache.ts +85 -0
- package/src/services/agent-transcript/agent-transcript-session-hub-activity.ts +103 -0
- package/src/services/agent-transcript/agent-transcript-session-hub-read.ts +194 -0
- package/src/services/agent-transcript/agent-transcript-session-hub-types.ts +81 -0
- package/src/services/agent-transcript/agent-transcript-session-hub.ts +196 -0
- package/src/services/agent-transcript/agent-transcript-sources.ts +199 -0
- package/src/services/agent-transcript/agent-transcript-ws-like.ts +19 -0
- package/src/services/agent-transcript/claude-projects-root.ts +21 -0
- package/src/services/agent-transcript/session-ownership.ts +152 -0
- package/src/services/nested-subagent-spy.ts +3 -3
- package/src/services/subagent-transcript-merger.ts +21 -7
- package/src/shared/agent-transcript-protocol.ts +158 -0
- package/src/types/chat.ts +27 -3
- package/src/web/app.tsx +5 -5
- package/src/web/components/chat/agent-card-summary.tsx +73 -0
- package/src/web/components/chat/agent-session-context.ts +34 -0
- package/src/web/components/chat/agent-session-fallback-store.ts +60 -0
- package/src/web/components/chat/agent-session-sheet-store.ts +23 -0
- package/src/web/components/chat/{team-member-sheet.tsx → agent-session-sheet.tsx} +12 -12
- package/src/web/components/chat/agent-session-window-content.tsx +133 -0
- package/src/web/components/chat/chat-history-bar.tsx +1 -0
- package/src/web/components/chat/chat-tab.tsx +21 -4
- package/src/web/components/chat/codex-usage-panel.tsx +26 -8
- package/src/web/components/chat/running-agents-bar.tsx +93 -0
- package/src/web/components/chat/team-activity-panel.tsx +19 -5
- package/src/web/components/chat/tool-cards.tsx +73 -4
- package/src/web/components/chat/use-open-agent-session.ts +83 -0
- package/src/web/components/floating-window/window-content-registry.ts +4 -5
- package/src/web/components/floating-window/window-geometry.ts +18 -0
- package/src/web/components/floating-window/window-persistence.ts +5 -4
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/settings/resolve-open-settings-action.ts +1 -1
- package/src/web/components/system/resolve-open-system-monitor-action.ts +1 -1
- package/src/web/hooks/use-agent-activity.ts +65 -0
- package/src/web/hooks/use-agent-session-stream.ts +134 -0
- package/src/web/hooks/use-chat.ts +12 -18
- package/src/web/hooks/use-global-events.ts +35 -2
- package/src/web/lib/agent-session-stream-merge.ts +48 -0
- package/src/web/lib/agent-step-summary.ts +331 -0
- package/src/web/lib/global-ws-channel.ts +41 -0
- package/src/web/lib/running-agent-rows.ts +115 -0
- package/dist/web/assets/accounts-settings-section-BNcPvEq1.js +0 -1
- package/dist/web/assets/accounts-settings-section-BNcPvEq1.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-BNcPvEq1.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-DfVRMUHS.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-DfVRMUHS.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-B_0-P8jx.js +0 -1
- package/dist/web/assets/ai-settings-section-Ezhw4Fef.js.br +0 -0
- package/dist/web/assets/ai-settings-section-Ezhw4Fef.js.gz +0 -0
- package/dist/web/assets/appearance-settings-section-CPOAp0fY.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-CPOAp0fY.js.gz +0 -0
- package/dist/web/assets/apps-panel-Cz7_o-M4.js.br +0 -0
- package/dist/web/assets/apps-panel-Cz7_o-M4.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-BPx3kHrM.js +0 -1
- package/dist/web/assets/branch-review-tab-BPx3kHrM.js.br +0 -0
- package/dist/web/assets/branch-review-tab-BPx3kHrM.js.gz +0 -0
- package/dist/web/assets/chat-tab-BadDM_j8.js +0 -1
- package/dist/web/assets/chat-tab-BadDM_j8.js.br +0 -0
- package/dist/web/assets/chat-tab-BadDM_j8.js.gz +0 -0
- package/dist/web/assets/chat-tab-IqAY4c7p.js +0 -15
- package/dist/web/assets/chat-tab-IqAY4c7p.js.br +0 -0
- package/dist/web/assets/chat-tab-IqAY4c7p.js.gz +0 -0
- package/dist/web/assets/code-editor-Dmitw9QS.js +0 -7
- package/dist/web/assets/code-editor-Dmitw9QS.js.br +0 -0
- package/dist/web/assets/code-editor-Dmitw9QS.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-D2hal4bG.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-D2hal4bG.js.gz +0 -0
- package/dist/web/assets/conflict-editor-Cj_gUncw.js.br +0 -0
- package/dist/web/assets/conflict-editor-Cj_gUncw.js.gz +0 -0
- package/dist/web/assets/csv-preview-Be3ODNj0.js.br +0 -0
- package/dist/web/assets/csv-preview-Be3ODNj0.js.gz +0 -0
- package/dist/web/assets/database-viewer-B1b--4y8.js.br +0 -0
- package/dist/web/assets/database-viewer-B1b--4y8.js.gz +0 -0
- package/dist/web/assets/design-tab-8wi0IHss.js.br +0 -0
- package/dist/web/assets/design-tab-8wi0IHss.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-D5-1Q8zQ.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-D5-1Q8zQ.js.gz +0 -0
- package/dist/web/assets/diff-viewer-Dcxk6UTG.js.br +0 -0
- package/dist/web/assets/diff-viewer-Dcxk6UTG.js.gz +0 -0
- package/dist/web/assets/edit-diff-preview-DxDAtYhi.js.br +0 -0
- package/dist/web/assets/editor-language-service-BVJQnTSV.js.br +0 -0
- package/dist/web/assets/editor-language-service-BVJQnTSV.js.gz +0 -0
- package/dist/web/assets/explorer-body-CTkrlrI3.js +0 -2
- package/dist/web/assets/explorer-body-CTkrlrI3.js.br +0 -0
- package/dist/web/assets/explorer-body-CTkrlrI3.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-DH5AD3Ng.js.br +0 -0
- package/dist/web/assets/explorer-window-content-DH5AD3Ng.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-BJr6ccWw.js.br +0 -0
- package/dist/web/assets/extension-manager-section-BJr6ccWw.js.gz +0 -0
- package/dist/web/assets/extension-webview-CYNXHd8D.js.br +0 -0
- package/dist/web/assets/extension-webview-CYNXHd8D.js.gz +0 -0
- package/dist/web/assets/files-settings-section-JX7o4lex.js.br +0 -0
- package/dist/web/assets/files-settings-section-JX7o4lex.js.gz +0 -0
- package/dist/web/assets/general-settings-section-kF94Mz5q.js.br +0 -0
- package/dist/web/assets/general-settings-section-kF94Mz5q.js.gz +0 -0
- package/dist/web/assets/git-log-panel-Crlp9ofM.js.br +0 -0
- package/dist/web/assets/git-log-panel-Crlp9ofM.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-O6MO5dKU.js.br +0 -0
- package/dist/web/assets/glide-data-grid-O6MO5dKU.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-DGLSGf37.js +0 -1
- package/dist/web/assets/group-chat-tab-DGLSGf37.js.br +0 -0
- package/dist/web/assets/group-chat-tab-DGLSGf37.js.gz +0 -0
- package/dist/web/assets/index-DQCw6d-2.js.br +0 -0
- package/dist/web/assets/index-DQCw6d-2.js.gz +0 -0
- package/dist/web/assets/keybindings-store-CJtEHFIQ.js +0 -1
- package/dist/web/assets/keyboard-shortcuts-section-CewWGwiF.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-CewWGwiF.js.gz +0 -0
- package/dist/web/assets/language-servers-section-nricdxke.js.br +0 -0
- package/dist/web/assets/language-servers-section-nricdxke.js.gz +0 -0
- package/dist/web/assets/lsp-client-DRMEWb-z.js.br +0 -0
- package/dist/web/assets/lsp-client-DRMEWb-z.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-D5-xliE9.js +0 -1
- package/dist/web/assets/markdown-renderer-Dab9Us6u.js.br +0 -0
- package/dist/web/assets/markdown-renderer-Dab9Us6u.js.gz +0 -0
- package/dist/web/assets/mobile-explorer-sheet-DtgdpvJ9.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-DtgdpvJ9.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-Dd5KDB_p.js.br +0 -0
- package/dist/web/assets/new-design-dialog-Dd5KDB_p.js.gz +0 -0
- package/dist/web/assets/notification-store-D2vTyp_Q.js +0 -1
- package/dist/web/assets/panel-store-DrSH3Nhp.js.br +0 -0
- package/dist/web/assets/panel-store-DrSH3Nhp.js.gz +0 -0
- package/dist/web/assets/performance-panel-LMGefOQ2.js +0 -3
- package/dist/web/assets/performance-panel-LMGefOQ2.js.br +0 -0
- package/dist/web/assets/performance-panel-LMGefOQ2.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-D67u6gPi.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-D67u6gPi.js.gz +0 -0
- package/dist/web/assets/problems-panel-DKgIw4uZ.js.br +0 -0
- package/dist/web/assets/problems-panel-DKgIw4uZ.js.gz +0 -0
- package/dist/web/assets/process-table-BIAgC_jG.js +0 -1
- package/dist/web/assets/process-table-BIAgC_jG.js.br +0 -0
- package/dist/web/assets/process-table-BIAgC_jG.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-CFiYcTBA.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-CFiYcTBA.js.gz +0 -0
- package/dist/web/assets/query-audit-section-BybgiPsc.js.br +0 -0
- package/dist/web/assets/query-audit-section-BybgiPsc.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Bs3fZSRw.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Bs3fZSRw.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DgxTdWMh.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-DgxTdWMh.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-DgxTdWMh.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DgGOfPVY.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DgGOfPVY.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-DOWryrQx.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-DOWryrQx.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-7t0wQsHx.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-7t0wQsHx.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-7t0wQsHx.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-DGhG-b5O.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-DGhG-b5O.js.gz +0 -0
- package/dist/web/assets/send-to-chat-B09VjsB5.js.br +0 -0
- package/dist/web/assets/send-to-chat-B09VjsB5.js.gz +0 -0
- package/dist/web/assets/services-panel-0klE3O4g.js.br +0 -0
- package/dist/web/assets/services-panel-0klE3O4g.js.gz +0 -0
- package/dist/web/assets/settings-body-CfqpqFfq.js.br +0 -0
- package/dist/web/assets/settings-body-CfqpqFfq.js.gz +0 -0
- package/dist/web/assets/settings-store-3jIBB8gx.js.br +0 -0
- package/dist/web/assets/settings-store-3jIBB8gx.js.gz +0 -0
- package/dist/web/assets/sql-query-editor-ALQ5_X0j.js.br +0 -0
- package/dist/web/assets/sql-query-editor-ALQ5_X0j.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-DKTty_jR.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-DKTty_jR.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-D6J6lq7-.js.br +0 -0
- package/dist/web/assets/system-monitor-body-D6J6lq7-.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-Di18zNNS.js +0 -1
- package/dist/web/assets/tab-host-window-content-DTcx2D0O.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-DTcx2D0O.js.gz +0 -0
- package/dist/web/assets/team-member-sheet-CFSbPG6n.js +0 -2
- package/dist/web/assets/team-member-sheet-CFSbPG6n.js.br +0 -0
- package/dist/web/assets/team-member-sheet-CFSbPG6n.js.gz +0 -0
- package/dist/web/assets/team-member-window-content-VeI6DGeM.js +0 -1
- package/dist/web/assets/team-member-window-content-VeI6DGeM.js.br +0 -0
- package/dist/web/assets/team-member-window-content-VeI6DGeM.js.gz +0 -0
- package/dist/web/assets/terminal-tab-DgTfQKBI.js.br +0 -0
- package/dist/web/assets/terminal-tab-DgTfQKBI.js.gz +0 -0
- package/dist/web/assets/tool-cards-_fpYI1hB.js +0 -9
- package/dist/web/assets/tool-cards-_fpYI1hB.js.br +0 -0
- package/dist/web/assets/tool-cards-_fpYI1hB.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-Dv111BKY.js.br +0 -0
- package/dist/web/assets/use-inline-blame-Dv111BKY.js.gz +0 -0
- package/dist/web/assets/use-lsp-CZTFJIXX.js.br +0 -0
- package/dist/web/assets/use-lsp-CZTFJIXX.js.gz +0 -0
- package/dist/web/assets/use-open-team-member-ElN3KmCI.js +0 -1
- package/dist/web/assets/use-process-kill-D7D9QvJG.js.br +0 -0
- package/dist/web/assets/use-process-kill-D7D9QvJG.js.gz +0 -0
- package/dist/web/assets/video-preview-DzwY3q9W.js.br +0 -0
- package/dist/web/assets/video-preview-DzwY3q9W.js.gz +0 -0
- package/dist/web/assets/voice-settings-section-B7zMHsuz.js.br +0 -0
- package/dist/web/assets/voice-settings-section-B7zMHsuz.js.gz +0 -0
- package/dist/web/assets/window-store-B4cbZ5rr.js +0 -1
- package/dist/web/assets/window-store-B4cbZ5rr.js.br +0 -0
- package/dist/web/assets/window-store-B4cbZ5rr.js.gz +0 -0
- package/src/web/components/chat/team-member-window-content.tsx +0 -66
- package/src/web/components/chat/team-working-bar.tsx +0 -62
- package/src/web/components/chat/use-open-team-member.ts +0 -41
- package/src/web/hooks/use-member-transcript-tail.ts +0 -95
- /package/dist/web/assets/{api-settings-BeINN5Dy.js → api-settings-BWgfXl7o.js} +0 -0
- /package/dist/web/assets/{api-settings-BeINN5Dy.js.br → api-settings-BWgfXl7o.js.br} +0 -0
- /package/dist/web/assets/{api-settings-BeINN5Dy.js.gz → api-settings-BWgfXl7o.js.gz} +0 -0
- /package/dist/web/assets/{libesm-CxeQSM3t.js → libesm-DAF5_F99.js} +0 -0
- /package/dist/web/assets/{libesm-CxeQSM3t.js.br → libesm-DAF5_F99.js.br} +0 -0
- /package/dist/web/assets/{libesm-CxeQSM3t.js.gz → libesm-DAF5_F99.js.gz} +0 -0
- /package/dist/web/assets/{onboarding-entry-DD3QSDiz.js → onboarding-entry-Bh-WE3vq.js} +0 -0
- /package/dist/web/assets/{onboarding-entry-DD3QSDiz.js.br → onboarding-entry-Bh-WE3vq.js.br} +0 -0
- /package/dist/web/assets/{onboarding-entry-DD3QSDiz.js.gz → onboarding-entry-Bh-WE3vq.js.gz} +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { ChatEvent } from "../provider.interface.ts";
|
|
2
|
+
import { stripSharedContext } from "../../shared/provider-context.ts";
|
|
3
|
+
import { redactTruncate } from "./codex-redact.ts";
|
|
4
|
+
import { completeLines, parseLine } from "./codex-rollout-header.ts";
|
|
5
|
+
import { mapRolloutItem } from "./codex-rollout-items.ts";
|
|
6
|
+
import { subagentToolResult, subagentToolUse } from "./codex-subagent-thread.ts";
|
|
7
|
+
import {
|
|
8
|
+
customToolCallToToolUse, fnCallToToolUse, fnOutputToToolResult, genericCallToToolUse,
|
|
9
|
+
} from "./codex-history.ts";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Line-fed parser for ONE codex rollout, for the live Agent-session window.
|
|
13
|
+
*
|
|
14
|
+
* Unlike `rolloutMessageSteps` (`codex-history.ts`) this never groups events
|
|
15
|
+
* into user/assistant turns and never answers a "load more" request — it just
|
|
16
|
+
* turns each newly-written record into flat step events, in the order they
|
|
17
|
+
* were written, as the file grows. It must never change how history renders,
|
|
18
|
+
* so it is a separate reader over the same schema rather than a refactor of
|
|
19
|
+
* the existing one.
|
|
20
|
+
*
|
|
21
|
+
* A child rollout writes its OWN `session_meta` first and its parent's second
|
|
22
|
+
* (a forked context) — only the first describes the file, so every session_meta
|
|
23
|
+
* after the first is ignored entirely here, the same as `readRolloutHeader` does.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* First cli_version observed (2026-10-01 survey of ~3400 local rollouts)
|
|
28
|
+
* writing the newer `item_completed` record instead of the legacy
|
|
29
|
+
* `response_item` pairs. Below this, a file is read the legacy way; at or
|
|
30
|
+
* above it, or when the version cannot be read at all, the newer/default path
|
|
31
|
+
* is used — an older writer's rollout has nothing later versions removed, so
|
|
32
|
+
* reading it as the newer shape would just find no item_completed records and
|
|
33
|
+
* emit nothing.
|
|
34
|
+
*/
|
|
35
|
+
const FIRST_ITEM_EVENTS_CLI_VERSION = "0.154.0";
|
|
36
|
+
|
|
37
|
+
function isOlderThan(version: string, floor: string): boolean {
|
|
38
|
+
const parts = (v: string) => v.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
39
|
+
const a = parts(version);
|
|
40
|
+
const b = parts(floor);
|
|
41
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
42
|
+
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
43
|
+
if (diff !== 0) return diff < 0;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** One step, wire-ready for `/ws/global` push. */
|
|
49
|
+
export interface TailEventEnvelope {
|
|
50
|
+
ev: ChatEvent;
|
|
51
|
+
/** Epoch ms, from the record's own timestamp (carried forward when absent). */
|
|
52
|
+
ts: number;
|
|
53
|
+
/** A later event reusing a `toolUseId` already emitted — the client updates
|
|
54
|
+
* that step in place (a tool_result answering an earlier tool_use is the
|
|
55
|
+
* common case) instead of appending a new one. */
|
|
56
|
+
replace?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface TailFeedResult {
|
|
60
|
+
events: TailEventEnvelope[];
|
|
61
|
+
/** The file rewrote its own history (compaction / rollback): the caller must
|
|
62
|
+
* drop everything it has for this parser and re-read the file from byte 0. */
|
|
63
|
+
reset?: boolean;
|
|
64
|
+
/** Thread ids of subagents spawned since the previous `feed()` call, so the
|
|
65
|
+
* caller can start tailing their rollouts alongside this one. */
|
|
66
|
+
links: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Redact the input of a tool_use whose tool name is an unmapped item/call
|
|
70
|
+
* type verbatim — the generic fallback that would otherwise carry through
|
|
71
|
+
* whatever fields an unrecognized record happened to have, unredacted. */
|
|
72
|
+
function redactIfGeneric(ev: ChatEvent, rawType: string): ChatEvent {
|
|
73
|
+
if (ev.type === "tool_use" && ev.tool === rawType) {
|
|
74
|
+
return { ...ev, input: redactTruncate(ev.input) };
|
|
75
|
+
}
|
|
76
|
+
return ev;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Always-generic legacy-path call (its tool name already comes from `p.name`,
|
|
80
|
+
* there is nothing more specific to compare it against). */
|
|
81
|
+
function redactGenericCall(ev: ChatEvent): ChatEvent {
|
|
82
|
+
return ev.type === "tool_use" ? { ...ev, input: redactTruncate(ev.input) } : ev;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function createRolloutTailParser(): { feed(chunk: string): TailFeedResult } {
|
|
86
|
+
// Raw text not yet terminated by a newline — carried to the next feed() call
|
|
87
|
+
// rather than parsed early, so a chunk boundary landing mid-line (or mid
|
|
88
|
+
// multi-byte character) never produces a corrupt partial record.
|
|
89
|
+
let pending = "";
|
|
90
|
+
let headerSeen = false;
|
|
91
|
+
let format: "item-events" | "legacy" = "item-events";
|
|
92
|
+
const seenIds = new Set<string>();
|
|
93
|
+
let lastTs = 0;
|
|
94
|
+
|
|
95
|
+
function resolveTs(raw: unknown): number {
|
|
96
|
+
if (typeof raw === "string") {
|
|
97
|
+
const parsed = Date.parse(raw);
|
|
98
|
+
if (!Number.isNaN(parsed)) { lastTs = parsed; return parsed; }
|
|
99
|
+
}
|
|
100
|
+
return lastTs;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function push(out: TailEventEnvelope[], ev: ChatEvent, ts: number): void {
|
|
104
|
+
const id = "toolUseId" in ev ? ev.toolUseId : undefined;
|
|
105
|
+
if (id) {
|
|
106
|
+
if (seenIds.has(id)) { out.push({ ev, ts, replace: true }); return; }
|
|
107
|
+
seenIds.add(id);
|
|
108
|
+
}
|
|
109
|
+
out.push({ ev, ts });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function reset(out: TailEventEnvelope[]): void {
|
|
113
|
+
// The window is about to be rebuilt from byte 0, so every id emitted so
|
|
114
|
+
// far is no longer "already sent" from the caller's point of view either.
|
|
115
|
+
seenIds.clear();
|
|
116
|
+
out.length = 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function feed(chunk: string): TailFeedResult {
|
|
120
|
+
pending += chunk;
|
|
121
|
+
const lastNl = pending.lastIndexOf("\n");
|
|
122
|
+
const ready = lastNl >= 0 ? pending.slice(0, lastNl + 1) : "";
|
|
123
|
+
pending = lastNl >= 0 ? pending.slice(lastNl + 1) : pending;
|
|
124
|
+
|
|
125
|
+
const out: TailEventEnvelope[] = [];
|
|
126
|
+
const links: string[] = [];
|
|
127
|
+
const linkSeen = new Set<string>();
|
|
128
|
+
let didReset = false;
|
|
129
|
+
|
|
130
|
+
for (const line of completeLines(ready)) {
|
|
131
|
+
const rec = parseLine(line);
|
|
132
|
+
if (!rec) continue;
|
|
133
|
+
const p = rec.payload ?? {};
|
|
134
|
+
const ts = resolveTs(rec.timestamp);
|
|
135
|
+
|
|
136
|
+
if (rec.type === "session_meta") {
|
|
137
|
+
if (!headerSeen) {
|
|
138
|
+
headerSeen = true;
|
|
139
|
+
const cliVersion = typeof p.cli_version === "string" ? p.cli_version : undefined;
|
|
140
|
+
format = cliVersion && isOlderThan(cliVersion, FIRST_ITEM_EVENTS_CLI_VERSION) ? "legacy" : "item-events";
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (rec.type === "compacted") {
|
|
146
|
+
reset(out);
|
|
147
|
+
didReset = true;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (rec.type === "event_msg") {
|
|
152
|
+
if (p.type === "thread_rolled_back") {
|
|
153
|
+
reset(out);
|
|
154
|
+
didReset = true;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (p.type === "user_message" && typeof p.message === "string") {
|
|
158
|
+
push(out, { type: "text", content: stripSharedContext(p.message) }, ts);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (p.type === "agent_message" && typeof p.message === "string") {
|
|
162
|
+
push(out, { type: "text", content: p.message }, ts);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (p.type === "item_completed" && format === "item-events") {
|
|
166
|
+
const item = p.item;
|
|
167
|
+
const rawType = item && typeof item === "object" && typeof (item as Record<string, unknown>).type === "string"
|
|
168
|
+
? (item as Record<string, unknown>).type as string
|
|
169
|
+
: "";
|
|
170
|
+
const mapped = mapRolloutItem(item);
|
|
171
|
+
if (mapped.kind === "user") {
|
|
172
|
+
push(out, { type: "text", content: stripSharedContext(mapped.text) }, ts);
|
|
173
|
+
} else if (mapped.kind === "assistant") {
|
|
174
|
+
push(out, { type: "text", content: mapped.text }, ts);
|
|
175
|
+
} else if (mapped.kind === "events") {
|
|
176
|
+
for (const ev of mapped.events) push(out, redactIfGeneric(ev, rawType), ts);
|
|
177
|
+
} else if (mapped.kind === "subagent") {
|
|
178
|
+
const { activity } = mapped;
|
|
179
|
+
if (!linkSeen.has(activity.threadId)) { linkSeen.add(activity.threadId); links.push(activity.threadId); }
|
|
180
|
+
// No `children`: the window renders nested agents flat, following
|
|
181
|
+
// in timestamp order as their own steps rather than nested inline.
|
|
182
|
+
push(out, activity.done ? subagentToolResult(activity) : subagentToolUse(activity), ts);
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (rec.type === "response_item" && format === "legacy") {
|
|
190
|
+
if (p.type === "function_call") push(out, fnCallToToolUse(p), ts);
|
|
191
|
+
else if (p.type === "function_call_output") push(out, fnOutputToToolResult(p), ts);
|
|
192
|
+
else if (p.type === "custom_tool_call") push(out, customToolCallToToolUse(p), ts);
|
|
193
|
+
else if (p.type === "custom_tool_call_output") push(out, fnOutputToToolResult(p), ts);
|
|
194
|
+
else if (typeof p.type === "string" && p.type.endsWith("_call_output")) push(out, fnOutputToToolResult(p), ts);
|
|
195
|
+
else if (typeof p.type === "string" && p.type.endsWith("_call")) push(out, redactGenericCall(genericCallToToolUse(p)), ts);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { events: out, links, ...(didReset ? { reset: true as const } : {}) };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { feed };
|
|
203
|
+
}
|
|
@@ -62,7 +62,12 @@ export function subagentCardId(threadId: string): string {
|
|
|
62
62
|
return `subagent-${threadId}`;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/** The spawn, as the Agent card the chat already knows how to expand.
|
|
65
|
+
/** The spawn, as the Agent card the chat already knows how to expand.
|
|
66
|
+
*
|
|
67
|
+
* `transcript` is only ever passed by a history reader that already found and parsed the
|
|
68
|
+
* child's rollout — live streaming (`codex-event-mapper.ts`) and the tail parser call this
|
|
69
|
+
* with none, so `transcriptAvailable` stays unset there and the chat keeps this card's full
|
|
70
|
+
* live children instead of slimming away a source it cannot later stream from disk. */
|
|
66
71
|
export function subagentToolUse(activity: SubagentActivity, transcript?: SubagentTranscript | null): ChatEvent {
|
|
67
72
|
return {
|
|
68
73
|
type: "tool_use",
|
|
@@ -70,6 +75,7 @@ export function subagentToolUse(activity: SubagentActivity, transcript?: Subagen
|
|
|
70
75
|
input: { description: activity.path },
|
|
71
76
|
toolUseId: subagentCardId(activity.threadId),
|
|
72
77
|
...(transcript?.events.length ? { children: transcript.events } : {}),
|
|
78
|
+
...(transcript ? { transcriptAvailable: true } : {}),
|
|
73
79
|
};
|
|
74
80
|
}
|
|
75
81
|
|
package/src/server/index.ts
CHANGED
|
@@ -926,8 +926,13 @@ if (process.argv.includes("__serve__")) {
|
|
|
926
926
|
|
|
927
927
|
if (url.pathname === "/ws/global") {
|
|
928
928
|
// App-wide event bus: owns file watching + cross-cutting broadcasts, so
|
|
929
|
-
// they no longer depend on a chat tab being mounted.
|
|
930
|
-
|
|
929
|
+
// they no longer depend on a chat tab being mounted. The token is
|
|
930
|
+
// snapshotted here — not re-read from config on every push — so the
|
|
931
|
+
// agent-transcript hub can tell a long-lived socket apart from one
|
|
932
|
+
// whose password/token has since been rotated.
|
|
933
|
+
const authConfig = configService.get("auth");
|
|
934
|
+
const token = authConfig.enabled ? (url.searchParams.get("token") ?? null) : null;
|
|
935
|
+
const upgraded = server.upgrade(req, { data: { type: "global", token } });
|
|
931
936
|
if (upgraded) return undefined;
|
|
932
937
|
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
933
938
|
}
|
|
@@ -24,6 +24,8 @@ import { aggregateTasks } from "../../services/task-status-aggregator.ts";
|
|
|
24
24
|
import { MANY_IMAGE_DIMENSION_LIMIT, type StripMode } from "../../services/transcript-images.ts";
|
|
25
25
|
import { auditTranscriptImagesFile, stripTranscriptImagesFile } from "../../services/transcript-images-file.ts";
|
|
26
26
|
import { listCodexAccounts } from "../../services/codex-account.service.ts";
|
|
27
|
+
import { codexUsageSource } from "../../providers/codex-app-server/codex-usage-source.ts";
|
|
28
|
+
import { invalidateUsage, refreshUsage, registerUsageSource } from "../../services/provider-usage/usage-registry.ts";
|
|
27
29
|
import { findRolloutByThreadId } from "../../providers/codex-app-server/codex-history.ts";
|
|
28
30
|
import { getSessionProjectPath, setSessionMetadata, setSessionTitle, getSessionTitle, getPinnedSessionIds, pinSession, unpinSession, deleteSessionMapping, deleteSessionMetadata, deleteSessionTitle, getAllUnread, clearSessionUnread, setSessionUnread } from "../../services/db.service.ts";
|
|
29
31
|
import { setSessionTag, bulkSetSessionTag, getTagById, getSessionTags, getProjectDefaultTagId } from "../../services/tag.service.ts";
|
|
@@ -104,13 +106,19 @@ chatRoutes.get("/usage", async (c) => {
|
|
|
104
106
|
const sessionId = c.req.query("session");
|
|
105
107
|
const accountId = c.req.query("accountId");
|
|
106
108
|
if (c.req.query("refresh")) {
|
|
107
|
-
// `?refresh=1` is the user pressing refresh, so drop the cached value first — otherwise
|
|
108
|
-
// a provider's own getUsage answers from cache and the button does nothing visible.
|
|
109
109
|
if (providerId && providerId !== "claude") {
|
|
110
110
|
const provider = providerRegistry.get(providerId);
|
|
111
111
|
if (provider?.getUsage) {
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
if (providerId === "codex") {
|
|
113
|
+
// A memory-only invalidation promotes an old database snapshot on the next read.
|
|
114
|
+
// Fetch every account live; parallel reads stay within the client's 30s timeout.
|
|
115
|
+
registerUsageSource(codexUsageSource);
|
|
116
|
+
const accounts = codexUsageSource.listAccountIds();
|
|
117
|
+
const ids = accounts.length > 0 ? accounts : [""];
|
|
118
|
+
await Promise.all(ids.map((id) => refreshUsage("codex", id)));
|
|
119
|
+
} else {
|
|
120
|
+
invalidateUsage(providerId);
|
|
121
|
+
}
|
|
114
122
|
}
|
|
115
123
|
} else {
|
|
116
124
|
try { await refreshUsageNow(); } catch { /* use stale cache */ }
|
package/src/server/ws/global.ts
CHANGED
|
@@ -21,10 +21,20 @@ import { startWatching, stopWatching, onFileChange } from "../../services/file-w
|
|
|
21
21
|
import { configService } from "../../services/config.service.ts";
|
|
22
22
|
import { onDesignEvent } from "../../services/design/design-events.ts";
|
|
23
23
|
import { resolve } from "node:path";
|
|
24
|
+
import {
|
|
25
|
+
handleAgentActivitySubscribe, handleAgentActivityUnsubscribe,
|
|
26
|
+
handleAgentTranscriptClientClosed, handleAgentTranscriptPing,
|
|
27
|
+
handleAgentTranscriptSubscribe, handleAgentTranscriptUnsubscribe,
|
|
28
|
+
} from "../../services/agent-transcript/agent-transcript-hub.ts";
|
|
29
|
+
import type {
|
|
30
|
+
AgentActivitySubscribeMsg, AgentActivityUnsubscribeMsg,
|
|
31
|
+
AgentTranscriptSubscribeMsg, AgentTranscriptUnsubscribeMsg,
|
|
32
|
+
} from "../../shared/agent-transcript-protocol.ts";
|
|
24
33
|
|
|
25
34
|
type GlobalWsSocket = {
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
/** Auth token snapshotted at upgrade (`server/index.ts`) — re-checked on every agent-transcript push. */
|
|
36
|
+
data: { type: string; token?: string | null };
|
|
37
|
+
send: (data: string) => number;
|
|
28
38
|
};
|
|
29
39
|
|
|
30
40
|
const clients = new Set<GlobalWsSocket>();
|
|
@@ -102,11 +112,17 @@ export const globalWebSocket = {
|
|
|
102
112
|
return;
|
|
103
113
|
}
|
|
104
114
|
// Sent on connect and whenever the active project changes.
|
|
105
|
-
if (msg.type === "watch") setWatch(ws, msg.projectName ?? "");
|
|
115
|
+
if (msg.type === "watch") return setWatch(ws, msg.projectName ?? "");
|
|
116
|
+
if (msg.type === "agent-transcript:subscribe") return handleAgentTranscriptSubscribe(ws, msg as AgentTranscriptSubscribeMsg);
|
|
117
|
+
if (msg.type === "agent-transcript:unsubscribe") return handleAgentTranscriptUnsubscribe(ws, msg as AgentTranscriptUnsubscribeMsg);
|
|
118
|
+
if (msg.type === "agent-activity:subscribe") return handleAgentActivitySubscribe(ws, msg as AgentActivitySubscribeMsg);
|
|
119
|
+
if (msg.type === "agent-activity:unsubscribe") return handleAgentActivityUnsubscribe(ws, msg as AgentActivityUnsubscribeMsg);
|
|
120
|
+
if (msg.type === "ping") return handleAgentTranscriptPing(ws);
|
|
106
121
|
},
|
|
107
122
|
|
|
108
123
|
close(ws: GlobalWsSocket) {
|
|
109
124
|
releaseWatch(ws);
|
|
125
|
+
handleAgentTranscriptClientClosed(ws);
|
|
110
126
|
clients.delete(ws);
|
|
111
127
|
},
|
|
112
128
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Who is running right now" for one session, for the running-agents bar.
|
|
3
|
+
* Liveness is decided from transcript write time on disk, never from chat
|
|
4
|
+
* state in memory — the same signal `member-activity.service.ts` already uses
|
|
5
|
+
* for the team panel, so a resumed, backgrounded or reopened session reads the
|
|
6
|
+
* same way as one still streaming live.
|
|
7
|
+
*/
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { getCachedSubagentGroups, getCachedTranscriptsByMember } from "./agent-transcript-index-cache.ts";
|
|
10
|
+
import { scanCodexDescendants } from "./agent-transcript-codex-activity.ts";
|
|
11
|
+
import { summarizeTranscriptTail } from "../team-member-activity/transcript-tail-summary.ts";
|
|
12
|
+
import { subagentCardId } from "../../providers/codex-app-server/codex-subagent-thread.ts";
|
|
13
|
+
import type { OwnedSession } from "./session-ownership.ts";
|
|
14
|
+
import { ACTIVITY_WINDOW_MS, type AgentTranscriptRunningEntry } from "../../shared/agent-transcript-protocol.ts";
|
|
15
|
+
|
|
16
|
+
function lastStepLabel(tail: Awaited<ReturnType<typeof summarizeTranscriptTail>>): string | undefined {
|
|
17
|
+
return tail.lastNarrative ?? tail.lastTool;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function claudeActivity(owned: OwnedSession, now: number): Promise<AgentTranscriptRunningEntry[]> {
|
|
21
|
+
const subagentsDir = join(owned.claude!.sessionDir, "subagents");
|
|
22
|
+
const groups = getCachedSubagentGroups(subagentsDir);
|
|
23
|
+
const out: AgentTranscriptRunningEntry[] = [];
|
|
24
|
+
|
|
25
|
+
for (const [cardId, entries] of groups) {
|
|
26
|
+
let newest = entries[0];
|
|
27
|
+
for (const e of entries) if (e.modifiedAt > newest!.modifiedAt) newest = e;
|
|
28
|
+
if (!newest || now - newest.modifiedAt > ACTIVITY_WINDOW_MS) continue;
|
|
29
|
+
const tail = await summarizeTranscriptTail(newest.transcriptPath);
|
|
30
|
+
out.push({ cardId, lastWriteAt: newest.modifiedAt, lastStep: lastStepLabel(tail) });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// A team is itself a session: its members carry no toolUseId (they are not
|
|
34
|
+
// card-spawned), so they never appear in `groups` above and need their own pass.
|
|
35
|
+
for (const [memberName, entry] of getCachedTranscriptsByMember(subagentsDir)) {
|
|
36
|
+
if (!entry.sizeBytes || now - entry.modifiedAt > ACTIVITY_WINDOW_MS) continue;
|
|
37
|
+
const tail = await summarizeTranscriptTail(entry.transcriptPath);
|
|
38
|
+
out.push({ memberName, lastWriteAt: entry.modifiedAt, lastStep: lastStepLabel(tail) });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function codexActivity(owned: OwnedSession, now: number): AgentTranscriptRunningEntry[] {
|
|
45
|
+
const { path, dirs } = owned.codex!;
|
|
46
|
+
const descendants = scanCodexDescendants(path, dirs, owned.projectPath);
|
|
47
|
+
const out: AgentTranscriptRunningEntry[] = [];
|
|
48
|
+
for (const d of descendants) {
|
|
49
|
+
// Age out on staleness alone, not "done AND stale": a child that crashed
|
|
50
|
+
// before ever writing its own completed record would otherwise show as
|
|
51
|
+
// running forever, since `done` would never become true for it.
|
|
52
|
+
if (now - d.lastWriteAt > ACTIVITY_WINDOW_MS) continue;
|
|
53
|
+
out.push({ cardId: subagentCardId(d.threadId), lastWriteAt: d.lastWriteAt, lastStep: d.lastStep });
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Test-only: counts calls to `computeRunningAgents`, so a test can prove the
|
|
59
|
+
* hub computes this once per tick and fans the SAME result out to every
|
|
60
|
+
* subscriber, rather than recomputing it once per subscriber. */
|
|
61
|
+
let callCountForTest = 0;
|
|
62
|
+
export function _resetComputeRunningAgentsCallCountForTest(): void {
|
|
63
|
+
callCountForTest = 0;
|
|
64
|
+
}
|
|
65
|
+
export function _computeRunningAgentsCallCountForTest(): number {
|
|
66
|
+
return callCountForTest;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function computeRunningAgents(owned: OwnedSession, now: number): Promise<AgentTranscriptRunningEntry[]> {
|
|
70
|
+
callCountForTest++;
|
|
71
|
+
return owned.providerId === "claude" ? claudeActivity(owned, now) : codexActivity(owned, now);
|
|
72
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex side of the running-agents feed: which threads the session has spawned,
|
|
3
|
+
* and what each was last doing — read from bounded tails, not full parses.
|
|
4
|
+
*
|
|
5
|
+
* A direct child's spawn/completion is recorded as a `SubAgentActivity` item
|
|
6
|
+
* in the ROOT session's own rollout (not in the child's file), so discovering
|
|
7
|
+
* "what has this session spawned" means reading a slice of the root rollout
|
|
8
|
+
* itself. A grandchild's spawn is recorded the same way, but inside the
|
|
9
|
+
* child's own rollout — so once a child is known, its tail is read the same
|
|
10
|
+
* way to find further descendants, bounded in both count and depth so a
|
|
11
|
+
* runaway spawn loop cannot make this unbounded work.
|
|
12
|
+
*/
|
|
13
|
+
import { dirname } from "node:path";
|
|
14
|
+
import { cachedReadTailText, mtimeOf } from "./agent-transcript-codex-tail-cache.ts";
|
|
15
|
+
import { agentTranscriptClock } from "./agent-transcript-hub-clock.ts";
|
|
16
|
+
import { completeLines, parseLine } from "../../providers/codex-app-server/codex-rollout-header.ts";
|
|
17
|
+
import { parseSubagentActivity } from "../../providers/codex-app-server/codex-subagent-thread.ts";
|
|
18
|
+
import { mapRolloutItem } from "../../providers/codex-app-server/codex-rollout-items.ts";
|
|
19
|
+
import { findRolloutByThreadId, isCodexRolloutPath } from "../../providers/codex-app-server/codex-history.ts";
|
|
20
|
+
import { INDEX_REFRESH_MS } from "../../shared/agent-transcript-protocol.ts";
|
|
21
|
+
|
|
22
|
+
/** How many spawn levels deep the descendant scan follows before giving up. */
|
|
23
|
+
const MAX_DESCENDANT_DEPTH = 8;
|
|
24
|
+
/** Hard cap so a runaway spawn loop cannot make this scan unbounded. */
|
|
25
|
+
const MAX_DESCENDANTS = 64;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* `findRolloutByThreadId` walks every rollout under a dir with a recursive
|
|
29
|
+
* `readdirSync` — fine for a one-off lookup, not for one per descendant on a
|
|
30
|
+
* 3s activity tick with a thousand-rollout `~/.codex/sessions`. Cached by
|
|
31
|
+
* (dir, threadId) for the same window the index cache uses elsewhere, and a
|
|
32
|
+
* miss is cached too so a thread that never resolves doesn't re-scan forever.
|
|
33
|
+
*/
|
|
34
|
+
const FILE_LOOKUP_TTL_MS = INDEX_REFRESH_MS;
|
|
35
|
+
const MAX_LOOKUP_CACHE_ENTRIES = 1000;
|
|
36
|
+
interface LookupEntry { expiresAt: number; file: string | null; }
|
|
37
|
+
const lookupCache = new Map<string, LookupEntry>();
|
|
38
|
+
|
|
39
|
+
function evictLookupCacheIfOverCapacity(now: number): void {
|
|
40
|
+
if (lookupCache.size < MAX_LOOKUP_CACHE_ENTRIES) return;
|
|
41
|
+
for (const [key, entry] of lookupCache) {
|
|
42
|
+
if (entry.expiresAt <= now) lookupCache.delete(key);
|
|
43
|
+
}
|
|
44
|
+
while (lookupCache.size >= MAX_LOOKUP_CACHE_ENTRIES) {
|
|
45
|
+
const oldest = lookupCache.keys().next().value;
|
|
46
|
+
if (oldest === undefined) break;
|
|
47
|
+
lookupCache.delete(oldest);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cachedFindRolloutByThreadId(dir: string, threadId: string, projectPath: string): string | null {
|
|
52
|
+
const now = agentTranscriptClock.now();
|
|
53
|
+
const key = `${dir}\0${threadId}`;
|
|
54
|
+
const hit = lookupCache.get(key);
|
|
55
|
+
if (hit && hit.expiresAt > now) return hit.file;
|
|
56
|
+
evictLookupCacheIfOverCapacity(now);
|
|
57
|
+
const file = findRolloutByThreadId(dir, threadId, projectPath);
|
|
58
|
+
lookupCache.set(key, { expiresAt: now + FILE_LOOKUP_TTL_MS, file });
|
|
59
|
+
return file;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Test-only: forget every cached lookup. */
|
|
63
|
+
export function _resetCodexDescendantLookupCache(): void {
|
|
64
|
+
lookupCache.clear();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface CodexDescendant {
|
|
68
|
+
threadId: string;
|
|
69
|
+
path: string;
|
|
70
|
+
done: boolean;
|
|
71
|
+
lastWriteAt: number;
|
|
72
|
+
lastStep?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A short, one-line label for the most recent tool call in a tail of rollout records. */
|
|
76
|
+
function lastStepFromLines(lines: string[]): string | undefined {
|
|
77
|
+
let label: string | undefined;
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
const rec = parseLine(line);
|
|
80
|
+
if (!rec || rec.type !== "event_msg") continue;
|
|
81
|
+
const p = rec.payload ?? {};
|
|
82
|
+
if (p.type !== "item_completed") continue;
|
|
83
|
+
const mapped = mapRolloutItem(p.item);
|
|
84
|
+
if (mapped.kind !== "events") continue;
|
|
85
|
+
for (const ev of mapped.events) {
|
|
86
|
+
if (ev.type === "tool_use") label = ev.tool;
|
|
87
|
+
else if (ev.type === "text" && ev.content.trim()) label = ev.content.trim().split("\n")[0]!.slice(0, 160);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return label;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** `SubAgentActivity` items in a tail of rollout records, most-recent state per thread. */
|
|
94
|
+
function subagentActivityFromLines(lines: string[]): Map<string, boolean> {
|
|
95
|
+
const byThread = new Map<string, boolean>();
|
|
96
|
+
for (const line of lines) {
|
|
97
|
+
const rec = parseLine(line);
|
|
98
|
+
if (!rec || rec.type !== "event_msg") continue;
|
|
99
|
+
const p = rec.payload ?? {};
|
|
100
|
+
if (p.type !== "item_completed") continue;
|
|
101
|
+
const activity = parseSubagentActivity(p.item);
|
|
102
|
+
if (activity) byThread.set(activity.threadId, activity.done);
|
|
103
|
+
}
|
|
104
|
+
return byThread;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Descendant threads of one rollout file (direct children when `rolloutPath`
|
|
109
|
+
* is the session's own root, grandchildren when it is an already-known
|
|
110
|
+
* child), each resolved to its own file and last-write time.
|
|
111
|
+
*/
|
|
112
|
+
function directDescendants(rolloutPath: string, dirs: string[], projectPath: string): CodexDescendant[] {
|
|
113
|
+
const tail = cachedReadTailText(rolloutPath);
|
|
114
|
+
if (!tail) return [];
|
|
115
|
+
const lines = completeLines(tail.text);
|
|
116
|
+
const activity = subagentActivityFromLines(lines);
|
|
117
|
+
const out: CodexDescendant[] = [];
|
|
118
|
+
// A child almost always lands in the same spawn-day folder as the rollout
|
|
119
|
+
// that spawned it — trying that folder first turns the common case into a
|
|
120
|
+
// readdir of a few dozen files instead of the whole sessions tree, with the
|
|
121
|
+
// full dir list (each itself cached) still there as a fallback.
|
|
122
|
+
const searchDirs = [dirname(rolloutPath), ...dirs];
|
|
123
|
+
for (const [threadId, done] of activity) {
|
|
124
|
+
let file: string | null = null;
|
|
125
|
+
for (const dir of searchDirs) {
|
|
126
|
+
const found = cachedFindRolloutByThreadId(dir, threadId, projectPath);
|
|
127
|
+
if (found && isCodexRolloutPath(found)) { file = found; break; }
|
|
128
|
+
}
|
|
129
|
+
if (!file) continue;
|
|
130
|
+
const childTail = cachedReadTailText(file);
|
|
131
|
+
out.push({
|
|
132
|
+
threadId,
|
|
133
|
+
path: file,
|
|
134
|
+
done,
|
|
135
|
+
lastWriteAt: mtimeOf(file),
|
|
136
|
+
lastStep: childTail ? lastStepFromLines(completeLines(childTail.text)) : undefined,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Every descendant of `rootRolloutPath`, discovered breadth-first up to
|
|
144
|
+
* `MAX_DESCENDANT_DEPTH` levels and `MAX_DESCENDANTS` total threads.
|
|
145
|
+
*/
|
|
146
|
+
export function scanCodexDescendants(rootRolloutPath: string, dirs: string[], projectPath: string): CodexDescendant[] {
|
|
147
|
+
const seen = new Map<string, CodexDescendant>();
|
|
148
|
+
let frontier = [rootRolloutPath];
|
|
149
|
+
for (let depth = 0; depth < MAX_DESCENDANT_DEPTH && frontier.length > 0 && seen.size < MAX_DESCENDANTS; depth++) {
|
|
150
|
+
const next: string[] = [];
|
|
151
|
+
for (const path of frontier) {
|
|
152
|
+
for (const d of directDescendants(path, dirs, projectPath)) {
|
|
153
|
+
if (seen.has(d.threadId) || seen.size >= MAX_DESCENDANTS) continue;
|
|
154
|
+
seen.set(d.threadId, d);
|
|
155
|
+
next.push(d.path);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
frontier = next;
|
|
159
|
+
}
|
|
160
|
+
return [...seen.values()];
|
|
161
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached "read the tail of this rollout as text" for the Codex side of the
|
|
3
|
+
* running-agents feed. Split out of `agent-transcript-codex-activity.ts` to
|
|
4
|
+
* keep both files within the project's file-size guideline.
|
|
5
|
+
*
|
|
6
|
+
* The activity feed polls every 3s whether or not a descendant's rollout
|
|
7
|
+
* actually changed; re-reading 256KB of an unchanged file on every one of
|
|
8
|
+
* those ticks (up to 64 descendants deep) is exactly the "still cheap when
|
|
9
|
+
* idle" invariant this cache exists to protect. A result is only ever
|
|
10
|
+
* reused when the file's size AND mtime both still match what was read.
|
|
11
|
+
*/
|
|
12
|
+
import { statSync } from "node:fs";
|
|
13
|
+
import { agentTranscriptFsIo, statSizeSafe } from "./agent-transcript-fs-io.ts";
|
|
14
|
+
|
|
15
|
+
const TAIL_BYTES = 256 * 1024;
|
|
16
|
+
const MAX_TAIL_READ_CACHE_ENTRIES = 1000;
|
|
17
|
+
|
|
18
|
+
export function mtimeOf(path: string): number {
|
|
19
|
+
try {
|
|
20
|
+
return statSync(path).mtimeMs;
|
|
21
|
+
} catch {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read the tail of a file as text, dropping a possibly-partial first line.
|
|
28
|
+
* Never throws — a file that vanishes or locks up between the stat and the
|
|
29
|
+
* read (this runs on a 3s ticker, unattended) degrades to "nothing read"
|
|
30
|
+
* rather than reaching the caller and, through it, an un-awaited tick
|
|
31
|
+
* promise with nothing to catch its rejection.
|
|
32
|
+
*/
|
|
33
|
+
function readTailText(path: string): { text: string; size: number } | null {
|
|
34
|
+
const size = statSizeSafe(path);
|
|
35
|
+
if (size === null) return null;
|
|
36
|
+
const start = Math.max(0, size - TAIL_BYTES);
|
|
37
|
+
let buf: Buffer;
|
|
38
|
+
try {
|
|
39
|
+
buf = agentTranscriptFsIo.readRange(path, start, size - start);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const text = buf.toString("utf8");
|
|
44
|
+
// A mid-file read almost always starts inside a record; `completeLines`
|
|
45
|
+
// already drops an unterminated trailing line, this drops the stray
|
|
46
|
+
// leading one the same way a truncated head would.
|
|
47
|
+
return { text: start > 0 ? text.slice(text.indexOf("\n") + 1) : text, size };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface TailReadCacheEntry {
|
|
51
|
+
size: number;
|
|
52
|
+
mtimeMs: number;
|
|
53
|
+
result: { text: string; size: number } | null;
|
|
54
|
+
}
|
|
55
|
+
const tailReadCache = new Map<string, TailReadCacheEntry>();
|
|
56
|
+
|
|
57
|
+
function evictTailReadCacheIfOverCapacity(): void {
|
|
58
|
+
if (tailReadCache.size < MAX_TAIL_READ_CACHE_ENTRIES) return;
|
|
59
|
+
const oldest = tailReadCache.keys().next().value;
|
|
60
|
+
if (oldest !== undefined) tailReadCache.delete(oldest);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `readTailText`, but skips the 256KB read entirely when the file's size
|
|
65
|
+
* and mtime match what was read on a previous tick.
|
|
66
|
+
*/
|
|
67
|
+
export function cachedReadTailText(path: string): { text: string; size: number } | null {
|
|
68
|
+
const size = statSizeSafe(path);
|
|
69
|
+
if (size === null) {
|
|
70
|
+
tailReadCache.delete(path);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const mtimeMs = mtimeOf(path);
|
|
74
|
+
const hit = tailReadCache.get(path);
|
|
75
|
+
if (hit && hit.size === size && hit.mtimeMs === mtimeMs) return hit.result;
|
|
76
|
+
const result = readTailText(path);
|
|
77
|
+
evictTailReadCacheIfOverCapacity();
|
|
78
|
+
tailReadCache.set(path, { size, mtimeMs, result });
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Test-only: forget every cached tail read. */
|
|
83
|
+
export function _resetCodexTailReadCache(): void {
|
|
84
|
+
tailReadCache.clear();
|
|
85
|
+
}
|