@opengsd/gsd-pi 1.3.0-dev.65546769 → 1.3.0-dev.72e3af2a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +11 -2
- package/dist/resources/extensions/google-cli/stream-adapter.js +82 -15
- package/dist/resources/extensions/gsd/artifact-verification.js +427 -0
- package/dist/resources/extensions/gsd/auto/orchestrator.js +12 -3
- package/dist/resources/extensions/gsd/auto/session.js +3 -0
- package/dist/resources/extensions/gsd/auto-artifact-paths.js +28 -1
- package/dist/resources/extensions/gsd/auto-dispatch.js +20 -19
- package/dist/resources/extensions/gsd/auto-prompts.js +26 -11
- package/dist/resources/extensions/gsd/auto-recovery.js +6 -507
- package/dist/resources/extensions/gsd/auto-runtime-state.js +4 -5
- package/dist/resources/extensions/gsd/auto-timeout-recovery.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +103 -13
- package/dist/resources/extensions/gsd/bootstrap/core-session-tools.js +38 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +2 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +10 -19
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -19
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +68 -10
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- package/dist/resources/extensions/gsd/commands-context.js +19 -1
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +16 -10
- package/dist/resources/extensions/gsd/commands-worktree.js +12 -10
- package/dist/resources/extensions/gsd/dashboard-overlay.js +32 -3
- package/dist/resources/extensions/gsd/db/queries.js +60 -0
- package/dist/resources/extensions/gsd/db-workspace.js +55 -3
- package/dist/resources/extensions/gsd/doctor-providers.js +92 -8
- package/dist/resources/extensions/gsd/exec-sandbox.js +45 -9
- package/dist/resources/extensions/gsd/forensics.js +2 -32
- package/dist/resources/extensions/gsd/git-service.js +4 -4
- package/dist/resources/extensions/gsd/guided-flow-queue.js +66 -5
- package/dist/resources/extensions/gsd/health-widget.js +55 -29
- package/dist/resources/extensions/gsd/layout-policy.js +3 -1
- package/dist/resources/extensions/gsd/markdown-renderer.js +8 -9
- package/dist/resources/extensions/gsd/memory-consolidation-scanner.js +44 -21
- package/dist/resources/extensions/gsd/migration-auto-check.js +22 -0
- package/dist/resources/extensions/gsd/milestone-ids.js +32 -2
- package/dist/resources/extensions/gsd/milestone-implementation-evidence.js +26 -20
- package/dist/resources/extensions/gsd/prompts/code-review.md +6 -4
- package/dist/resources/extensions/gsd/quick.js +45 -2
- package/dist/resources/extensions/gsd/session-forensics.js +11 -1
- package/dist/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
- package/dist/resources/extensions/gsd/state/derive/cache.js +28 -0
- package/dist/resources/extensions/gsd/state/derive/db-open.js +39 -0
- package/dist/resources/extensions/gsd/state/derive/from-db.js +452 -0
- package/dist/resources/extensions/gsd/state/derive/index.js +75 -0
- package/dist/resources/extensions/gsd/state/derive/interrupted-work.js +21 -0
- package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +45 -2
- package/dist/resources/extensions/gsd/state-reconciliation/index.js +48 -23
- package/dist/resources/extensions/gsd/state-reconciliation/registry.js +32 -28
- package/dist/resources/extensions/gsd/state.js +12 -611
- package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -2
- package/dist/resources/extensions/gsd/tools/complete-task.js +43 -14
- package/dist/resources/extensions/gsd/tools/exec-tool.js +7 -2
- package/dist/resources/extensions/gsd/unit-context-composer.js +23 -7
- package/dist/resources/extensions/gsd/unit-registry.js +32 -4
- package/dist/resources/extensions/gsd/unmerged-milestone-guard.js +33 -3
- package/dist/resources/extensions/gsd/validation-block-guard.js +9 -4
- package/dist/resources/extensions/gsd/workflow-projections.js +19 -14
- package/dist/resources/extensions/gsd/workspace-git-preflight.js +30 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +44 -2
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/react-loadable-manifest.json +9 -9
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/{2659.b7b129ee6a769448.js → 2659.58e950899a9bb82f.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/2772.a7c1fcc69a4685ef.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{3616.3c60753b8ffcbd2e.js → 3616.61a2af74bb8833c8.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{4283.8e446784528ed9dc.js → 4283.d0d9e0a955e441cb.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{5826.a46ecdd1cfe8dabc.js → 5826.5421d66c72b9f34e.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/{8785.481aa5869991b760.js → 8785.e29b3134cab1d153.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/8937.640dc9c2aaa1dfad.js +10 -0
- package/dist/web/standalone/.next/static/chunks/app/{page-6644fc6ee8ca1247.js → page-72a856634ad14c10.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/webpack-9c401904f87ded16.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +1 -1
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/dist/workflow.d.ts +1 -0
- package/packages/contracts/dist/workflow.d.ts.map +1 -1
- package/packages/contracts/dist/workflow.js +2 -0
- package/packages/contracts/dist/workflow.js.map +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/dist/agent-session.d.ts +1 -0
- package/packages/gsd-agent-core/dist/agent-session.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/agent-session.js +3 -0
- package/packages/gsd-agent-core/dist/agent-session.js.map +1 -1
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts +41 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.js +62 -0
- package/packages/gsd-agent-core/dist/extension-ui-snapshot.js.map +1 -0
- package/packages/gsd-agent-core/dist/index.d.ts +2 -0
- package/packages/gsd-agent-core/dist/index.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/index.js +2 -0
- package/packages/gsd-agent-core/dist/index.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-events.js +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-events.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts +1 -0
- package/packages/gsd-agent-core/dist/session/agent-session-host.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-host.js.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts +5 -0
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js +60 -3
- package/packages/gsd-agent-core/dist/session/agent-session-prompt.js.map +1 -1
- package/packages/gsd-agent-core/dist/transcript-store.d.ts +58 -0
- package/packages/gsd-agent-core/dist/transcript-store.d.ts.map +1 -0
- package/packages/gsd-agent-core/dist/transcript-store.js +132 -0
- package/packages/gsd-agent-core/dist/transcript-store.js.map +1 -0
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +2 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts +4 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js +7 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller-latency.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts +3 -24
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +26 -829
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts +58 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js +312 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-handoff-filter.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts +31 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js +130 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-pinned-zone.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts +15 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js +258 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-segment-walker.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts +13 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js +118 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-tool-rollup.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts +9 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-state.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts +54 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js +20 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode-ui-state.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts +4 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js +9 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts +56 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js +44 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/streaming-render-state.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts +5 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.d.ts.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js +77 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/tui-transcript-tracker.js.map +1 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js +18 -0
- package/packages/gsd-agent-modes/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/README.md +1 -1
- package/packages/mcp-server/dist/server.d.ts +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +3 -3
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +13 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +34 -20
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/README.md +3 -2
- package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.js +94 -0
- package/packages/pi-coding-agent/dist/core/session-manager-context.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.js +244 -0
- package/packages/pi-coding-agent/dist/core/session-manager-list.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.js +84 -0
- package/packages/pi-coding-agent/dist/core/session-manager-migration.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts +135 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.js +2 -0
- package/packages/pi-coding-agent/dist/core/session-manager-types.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +6 -154
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +22 -459
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts +75 -75
- package/packages/pi-coding-agent/dist/theme/theme-schema.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.js +1 -1
- package/packages/pi-coding-agent/dist/theme/theme-schema.js.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/theme/theme.js +11 -7
- package/packages/pi-coding-agent/dist/theme/theme.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/dist/theme/theme-schema.d.ts +75 -75
- package/pkg/dist/theme/theme-schema.d.ts.map +1 -1
- package/pkg/dist/theme/theme-schema.js +1 -1
- package/pkg/dist/theme/theme-schema.js.map +1 -1
- package/pkg/dist/theme/theme.d.ts.map +1 -1
- package/pkg/dist/theme/theme.js +11 -7
- package/pkg/dist/theme/theme.js.map +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +20 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +80 -0
- package/src/resources/extensions/google-cli/stream-adapter.ts +106 -19
- package/src/resources/extensions/gsd/artifact-verification.ts +464 -0
- package/src/resources/extensions/gsd/auto/orchestrator.ts +25 -11
- package/src/resources/extensions/gsd/auto/session.ts +5 -0
- package/src/resources/extensions/gsd/auto-artifact-paths.ts +47 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +21 -23
- package/src/resources/extensions/gsd/auto-prompts.ts +38 -12
- package/src/resources/extensions/gsd/auto-recovery.ts +10 -508
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -5
- package/src/resources/extensions/gsd/auto-timeout-recovery.ts +3 -2
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +125 -12
- package/src/resources/extensions/gsd/bootstrap/core-session-tools.ts +43 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +6 -1
- package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +2 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +11 -19
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +52 -18
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +74 -10
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- package/src/resources/extensions/gsd/commands-context.ts +18 -1
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -9
- package/src/resources/extensions/gsd/commands-worktree.ts +12 -10
- package/src/resources/extensions/gsd/dashboard-overlay.ts +32 -3
- package/src/resources/extensions/gsd/db/queries.ts +79 -0
- package/src/resources/extensions/gsd/db-workspace.ts +61 -3
- package/src/resources/extensions/gsd/doctor-providers.ts +103 -9
- package/src/resources/extensions/gsd/exec-sandbox.ts +49 -9
- package/src/resources/extensions/gsd/forensics.ts +2 -33
- package/src/resources/extensions/gsd/git-service.ts +5 -5
- package/src/resources/extensions/gsd/guided-flow-queue.ts +89 -4
- package/src/resources/extensions/gsd/health-widget.ts +69 -32
- package/src/resources/extensions/gsd/layout-policy.ts +2 -1
- package/src/resources/extensions/gsd/markdown-renderer.ts +8 -11
- package/src/resources/extensions/gsd/memory-consolidation-scanner.ts +51 -19
- package/src/resources/extensions/gsd/migration-auto-check.ts +23 -0
- package/src/resources/extensions/gsd/milestone-ids.ts +31 -2
- package/src/resources/extensions/gsd/milestone-implementation-evidence.ts +35 -21
- package/src/resources/extensions/gsd/prompts/code-review.md +6 -4
- package/src/resources/extensions/gsd/quick.ts +43 -2
- package/src/resources/extensions/gsd/session-forensics.ts +11 -1
- package/src/resources/extensions/gsd/skills/gsd-headless/references/commands.md +1 -1
- package/src/resources/extensions/gsd/state/derive/cache.ts +46 -0
- package/src/resources/extensions/gsd/state/derive/db-open.ts +45 -0
- package/src/resources/extensions/gsd/state/derive/from-db.ts +561 -0
- package/src/resources/extensions/gsd/state/derive/index.ts +104 -0
- package/src/resources/extensions/gsd/state/derive/interrupted-work.ts +31 -0
- package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +81 -7
- package/src/resources/extensions/gsd/state-reconciliation/index.ts +50 -24
- package/src/resources/extensions/gsd/state-reconciliation/registry.ts +43 -28
- package/src/resources/extensions/gsd/state.ts +32 -732
- package/src/resources/extensions/gsd/tests/auto-artifact-paths.test.ts +98 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +111 -1
- package/src/resources/extensions/gsd/tests/commands-context.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/commands-gsd-core.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/commands-worktree-clean.test.ts +80 -0
- package/src/resources/extensions/gsd/tests/complete-slice.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/complete-task-rollback-evidence.test.ts +48 -8
- package/src/resources/extensions/gsd/tests/complete-task.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +55 -2
- package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/doctor-forensics-db-open-regression.test.ts +70 -2
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +107 -0
- package/src/resources/extensions/gsd/tests/exec-graceful-kill.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/exec-tool.test.ts +45 -1
- package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +88 -0
- package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +42 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +268 -3
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +119 -1
- package/src/resources/extensions/gsd/tests/integration/queue-active-milestone-context-budget.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/integration/quick-branch-lifecycle.test.ts +56 -9
- package/src/resources/extensions/gsd/tests/knowledge-cold-start.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/memory-consolidation-scanner.test.ts +78 -0
- package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/orchestrator-logs.test.ts +43 -1
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/parsers-legacy-importers.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +54 -1
- package/src/resources/extensions/gsd/tests/progressive-planning.test.ts +50 -14
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +195 -1
- package/src/resources/extensions/gsd/tests/read-uat-gate-verdict.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +56 -0
- package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +191 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +26 -0
- package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +193 -14
- package/src/resources/extensions/gsd/tests/unmerged-milestone-guard.test.ts +25 -0
- package/src/resources/extensions/gsd/tests/validation-block-guard.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +151 -2
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +39 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +2 -2
- package/src/resources/extensions/gsd/tools/complete-task.ts +53 -15
- package/src/resources/extensions/gsd/tools/exec-tool.ts +7 -3
- package/src/resources/extensions/gsd/unit-context-composer.ts +33 -7
- package/src/resources/extensions/gsd/unit-registry.ts +32 -4
- package/src/resources/extensions/gsd/unmerged-milestone-guard.ts +41 -5
- package/src/resources/extensions/gsd/validation-block-guard.ts +13 -7
- package/src/resources/extensions/gsd/workflow-projections.ts +20 -14
- package/src/resources/extensions/gsd/workspace-git-preflight.ts +31 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +41 -1
- package/dist/web/standalone/.next/static/chunks/2772.bfa657f49f955239.js +0 -1
- package/dist/web/standalone/.next/static/chunks/796.e0bdc932325d7e03.js +0 -10
- package/dist/web/standalone/.next/static/chunks/webpack-f46ea08200a0227e.js +0 -1
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{BTKtGFF1Y-hvVJEGhBRo9 → O7xDYXO0r4zFhIzY1hrWV}/_ssgManifest.js +0 -0
|
@@ -1,63 +1,17 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Interactive TUI chat stream controller.
|
|
3
|
-
import { Loader,
|
|
3
|
+
import { Loader, Spacer, Text } from "@gsd/pi-tui";
|
|
4
4
|
import { theme } from "@gsd/pi-coding-agent/theme/theme.js";
|
|
5
5
|
import { AssistantMessageComponent } from "../components/assistant-message.js";
|
|
6
6
|
import { reconcileChatTurnConnections } from "../components/chat-turn-connect.js";
|
|
7
|
-
import { ToolExecutionComponent
|
|
8
|
-
import { DynamicBorder } from "../components/dynamic-border.js";
|
|
7
|
+
import { ToolExecutionComponent } from "../components/tool-execution.js";
|
|
9
8
|
import { appKey } from "../components/keybinding-hints.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
// When providers reuse one assistant lifecycle across internal sub-turns,
|
|
17
|
-
// a content[] shrink resets renderedSegments. Keep the displaced segments so
|
|
18
|
-
// claude-code MCP pruning can remove stale provisional text later.
|
|
19
|
-
let orphanedSegments = [];
|
|
20
|
-
// Invocation matching only reconciles IDs reported by different event streams.
|
|
21
|
-
// Same-source identical invocations are separate concurrent tool calls.
|
|
22
|
-
const toolRegistrationSources = new WeakMap();
|
|
23
|
-
const PROVISIONAL_PRE_TOOL_ACTIONS = "check|inspect|look(?:\\s+into)?|read|open|search|grep|scan|run|verify|test|trace|review|investigate|reproduce|use|gather|find|pull|query|take a look|update|patch|edit|change|modify|write|create|add|remove|apply";
|
|
24
|
-
const FIRST_PERSON_PROVISIONAL_PRE_TOOL_RE = new RegExp(`^(?:i(?:'ll| will)|i(?:'m| am) going to|let me|i need to)\\s+(?:${PROVISIONAL_PRE_TOOL_ACTIONS})\\b`, "i");
|
|
25
|
-
const GERUND_PROVISIONAL_PRE_TOOL_RE = /^(?:checking|inspecting|reading|searching|running|verifying|testing|tracing|reviewing|investigating|scanning|updating|patching|editing|writing|creating|applying)\b/i;
|
|
26
|
-
function findPendingToolByInvocation(pendingTools, toolName, args, source) {
|
|
27
|
-
let fallback;
|
|
28
|
-
for (const component of pendingTools.values()) {
|
|
29
|
-
if (!component.isInFlight())
|
|
30
|
-
continue;
|
|
31
|
-
if (!component.matchesInvocation(toolName, args))
|
|
32
|
-
continue;
|
|
33
|
-
const sources = toolRegistrationSources.get(component);
|
|
34
|
-
if (!sources?.has(source)) {
|
|
35
|
-
return component;
|
|
36
|
-
}
|
|
37
|
-
if (sources.size > 1 && !fallback)
|
|
38
|
-
fallback = component;
|
|
39
|
-
}
|
|
40
|
-
return fallback;
|
|
41
|
-
}
|
|
42
|
-
function registerPendingToolComponent(host, toolCallId, toolName, args, source, createComponent) {
|
|
43
|
-
const existing = host.pendingTools.get(toolCallId);
|
|
44
|
-
if (existing) {
|
|
45
|
-
return { component: existing, created: false };
|
|
46
|
-
}
|
|
47
|
-
const matched = findPendingToolByInvocation(host.pendingTools, toolName, args, source);
|
|
48
|
-
if (matched) {
|
|
49
|
-
host.pendingTools.set(toolCallId, matched);
|
|
50
|
-
toolRegistrationSources.get(matched)?.add(source);
|
|
51
|
-
return { component: matched, created: false };
|
|
52
|
-
}
|
|
53
|
-
const component = createComponent();
|
|
54
|
-
component.setExpanded(host.toolOutputExpanded);
|
|
55
|
-
host.chatContainer.addChild(component);
|
|
56
|
-
markFirstVisibleAssistantOutput(host, "tool", { toolName, source });
|
|
57
|
-
host.pendingTools.set(toolCallId, component);
|
|
58
|
-
toolRegistrationSources.set(component, new Set([source]));
|
|
59
|
-
return { component, created: true };
|
|
60
|
-
}
|
|
9
|
+
import { markFirstVisibleAssistantOutput, markTuiLatency } from "./chat-controller-latency.js";
|
|
10
|
+
import { findLatestPinnableCandidates, findLatestPinnableText, tearDownPinnedZone, updatePinnedMessageZone, } from "./chat-pinned-zone.js";
|
|
11
|
+
import { hasAssistantToolBlocks, hasVisibleAssistantContent, isProvisionalPreToolProse, isRedundantDiscussRestatement, priorAssistantTextFromSession, shouldSuppressEntireAssistantMessage, textInvitesUserReply, } from "./chat-handoff-filter.js";
|
|
12
|
+
import { registerPendingToolComponent, replaceCompactToolRowsWithPhaseSummary, } from "./chat-tool-rollup.js";
|
|
13
|
+
import { applySubTurnContentShrink, rebuildSegmentsOnMessageEnd, runSegmentWalker, scanNewContentBlocks, } from "./chat-segment-walker.js";
|
|
14
|
+
export { findLatestPinnableCandidates, findLatestPinnableText, isProvisionalPreToolProse, isRedundantDiscussRestatement, priorAssistantTextFromSession, textInvitesUserReply, };
|
|
61
15
|
function startLoadingAnimation(host) {
|
|
62
16
|
if (host.pendingWorkingMessage === null) {
|
|
63
17
|
host.loadingAnimation = undefined;
|
|
@@ -69,7 +23,7 @@ function startLoadingAnimation(host) {
|
|
|
69
23
|
host.statusContainer.addChild(host.loadingAnimation);
|
|
70
24
|
if (host.pendingWorkingMessage !== undefined) {
|
|
71
25
|
if (host.pendingWorkingMessage) {
|
|
72
|
-
host.loadingAnimation
|
|
26
|
+
host.loadingAnimation?.setMessage?.(host.pendingWorkingMessage);
|
|
73
27
|
}
|
|
74
28
|
host.pendingWorkingMessage = undefined;
|
|
75
29
|
}
|
|
@@ -93,473 +47,16 @@ export function stopActivityIndicator(host) {
|
|
|
93
47
|
host.statusContainer.clear();
|
|
94
48
|
}
|
|
95
49
|
}
|
|
96
|
-
function markTuiLatency(host, phase, data) {
|
|
97
|
-
host.session?.markTurnLatency?.(phase, data);
|
|
98
|
-
}
|
|
99
|
-
function markFirstVisibleAssistantOutput(host, kind, data) {
|
|
100
|
-
host.session?.markFirstVisibleTurnLatency?.(kind, data);
|
|
101
|
-
}
|
|
102
|
-
function getVisibleTextLikeBlockType(block, hideThinkingBlock = false) {
|
|
103
|
-
if (block?.type === "text" && typeof block.text === "string" && block.text.trim().length > 0)
|
|
104
|
-
return "text";
|
|
105
|
-
if (hideThinkingBlock)
|
|
106
|
-
return undefined;
|
|
107
|
-
if (block?.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim().length > 0)
|
|
108
|
-
return "thinking";
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
/** True when assistant prose is handing off to the user (question or explicit invite). */
|
|
112
|
-
export function textInvitesUserReply(text) {
|
|
113
|
-
const trimmed = text.trim();
|
|
114
|
-
if (!trimmed)
|
|
115
|
-
return false;
|
|
116
|
-
if (/\?(?:\s|$)/m.test(trimmed))
|
|
117
|
-
return true;
|
|
118
|
-
return /\b(?:what do you want|what's on your mind|let me know|tell me what|help me understand)\b/i.test(trimmed);
|
|
119
|
-
}
|
|
120
|
-
const DISCUSS_RESTATE_RE = /\b(?:what do you want|what should we|before i can write|context file|placeholder name|need to understand what|what(?:'s| is) (?:on your mind|the next)|help me understand what you want)\b/i;
|
|
121
|
-
/** Second sub-turn that only says it is waiting after questions were already asked. */
|
|
122
|
-
const HANDOFF_WAIT_RESTATE_RE = /\b(?:holding\s+(?:here|for)|waiting\s+(?:here|for)|no\s+need\s+for\s+anything\s+else|until\s+you\s+(?:point|tell|let\s+me\s+know|answer|reply)|i(?:'ve| have)\s+asked)\b/i;
|
|
123
|
-
function isWaitOnlyQuestionFragment(fragment) {
|
|
124
|
-
return /^(?:i(?:'ve| have)\s+asked\b|(?:i'?m|i am)\s+(?:holding|waiting)\b|no\s+need\s+for\s+anything\s+else\b)/i.test(fragment)
|
|
125
|
-
&& !/\b(?:should|do you|would you|can we|could we|what|which|how|where|when|who|also|add|include)\b/i.test(fragment);
|
|
126
|
-
}
|
|
127
|
-
/** True when text adds a question beyond wait/hold boilerplate. */
|
|
128
|
-
function containsNewSubstantiveQuestion(text) {
|
|
129
|
-
for (let i = 0; i < text.length; i++) {
|
|
130
|
-
if (text[i] !== "?")
|
|
131
|
-
continue;
|
|
132
|
-
const previousBreak = Math.max(text.lastIndexOf("\n", i), text.lastIndexOf(".", i), text.lastIndexOf("!", i), text.lastIndexOf("?", i - 1), -1);
|
|
133
|
-
const fragment = text.slice(previousBreak + 1, i + 1).trim();
|
|
134
|
-
if (fragment.length < 8)
|
|
135
|
-
continue;
|
|
136
|
-
if (isWaitOnlyQuestionFragment(fragment))
|
|
137
|
-
continue;
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
function isHandoffWaitRestatement(next) {
|
|
143
|
-
if (!HANDOFF_WAIT_RESTATE_RE.test(next))
|
|
144
|
-
return false;
|
|
145
|
-
// Keep follow-ups that add a real question even when they also say holding/waiting.
|
|
146
|
-
if (containsNewSubstantiveQuestion(next))
|
|
147
|
-
return false;
|
|
148
|
-
// Only classify as a pure wait ack when the text is short; long text likely
|
|
149
|
-
// contains substantive content alongside incidental wait language.
|
|
150
|
-
if (next.length > 400)
|
|
151
|
-
return false;
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Claude Code can emit a second text sub-turn that restates the same milestone
|
|
156
|
-
* discuss ask. Drop it when the prior sub-turn already invited a user reply.
|
|
157
|
-
*/
|
|
158
|
-
export function isRedundantDiscussRestatement(priorText, newText) {
|
|
159
|
-
const prior = priorText.trim();
|
|
160
|
-
const next = newText.trim();
|
|
161
|
-
if (!prior || !next)
|
|
162
|
-
return false;
|
|
163
|
-
if (!textInvitesUserReply(prior))
|
|
164
|
-
return false;
|
|
165
|
-
const isDiscussRestate = DISCUSS_RESTATE_RE.test(next);
|
|
166
|
-
const isWaitRestate = isHandoffWaitRestatement(next);
|
|
167
|
-
if (!isDiscussRestate && !isWaitRestate)
|
|
168
|
-
return false;
|
|
169
|
-
// Wait acks are gated on length and no-? inside isHandoffWaitRestatement.
|
|
170
|
-
if (isWaitRestate)
|
|
171
|
-
return true;
|
|
172
|
-
if (next.length > prior.length * 1.1)
|
|
173
|
-
return false;
|
|
174
|
-
return next.length <= prior.length || next.length < 900;
|
|
175
|
-
}
|
|
176
|
-
function isSubTurnTextReplacement(blocks, rendered) {
|
|
177
|
-
for (const seg of rendered) {
|
|
178
|
-
if (seg.kind !== "text-run")
|
|
179
|
-
continue;
|
|
180
|
-
const oldText = (seg.cachedText ?? "").trim();
|
|
181
|
-
if (!oldText)
|
|
182
|
-
continue;
|
|
183
|
-
const newText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex).trim();
|
|
184
|
-
if (!newText || newText === oldText)
|
|
185
|
-
continue;
|
|
186
|
-
// Streaming growth extends prior text; a new sub-turn replaces it wholesale.
|
|
187
|
-
if (!newText.startsWith(oldText) && !oldText.startsWith(newText))
|
|
188
|
-
return seg.startIndex;
|
|
189
|
-
}
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
function getTextFromContentBlocks(blocks, startIndex, endIndex) {
|
|
193
|
-
const parts = [];
|
|
194
|
-
for (let i = startIndex; i <= endIndex && i < blocks.length; i++) {
|
|
195
|
-
const block = blocks[i];
|
|
196
|
-
if (block?.type === "text" && typeof block.text === "string" && block.text.trim()) {
|
|
197
|
-
parts.push(block.text.trim());
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return parts.join("\n\n");
|
|
201
|
-
}
|
|
202
|
-
function filterRedundantDiscussTextRuns(desired, blocks) {
|
|
203
|
-
const textRuns = desired.filter((seg) => seg.kind === "text-run" && seg.contentType === "text");
|
|
204
|
-
if (textRuns.length < 2)
|
|
205
|
-
return desired;
|
|
206
|
-
const skipStarts = new Set();
|
|
207
|
-
let lastKeptText;
|
|
208
|
-
for (const seg of textRuns) {
|
|
209
|
-
const text = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex);
|
|
210
|
-
if (lastKeptText && isRedundantDiscussRestatement(lastKeptText, text)) {
|
|
211
|
-
skipStarts.add(seg.startIndex);
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
lastKeptText = text;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return desired.filter((seg) => !(seg.kind === "text-run" && seg.contentType === "text" && skipStarts.has(seg.startIndex)));
|
|
218
|
-
}
|
|
219
|
-
function extractAssistantText(msg) {
|
|
220
|
-
if (!msg)
|
|
221
|
-
return "";
|
|
222
|
-
const content = msg.content;
|
|
223
|
-
if (typeof content === "string")
|
|
224
|
-
return content;
|
|
225
|
-
if (!Array.isArray(content))
|
|
226
|
-
return "";
|
|
227
|
-
const parts = [];
|
|
228
|
-
for (const block of content) {
|
|
229
|
-
if (!block || typeof block !== "object")
|
|
230
|
-
continue;
|
|
231
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
232
|
-
parts.push(block.text);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return parts.join("\n");
|
|
236
|
-
}
|
|
237
|
-
function latestPriorUserFacingText(orphaned, rendered) {
|
|
238
|
-
const runs = [...orphaned, ...rendered].filter((seg) => seg.kind === "text-run" && seg.contentType === "text");
|
|
239
|
-
return runs.at(-1)?.cachedText;
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Walk session history backward for the previous assistant prose block, skipping
|
|
243
|
-
* toolResult rows. Used when Claude Code emits a second assistant message
|
|
244
|
-
* (new timestamp) after tools in the same prompt.
|
|
245
|
-
*/
|
|
246
|
-
export function priorAssistantTextFromSession(messages, opts) {
|
|
247
|
-
let assistantFromEnd = 0;
|
|
248
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
249
|
-
const message = messages[i];
|
|
250
|
-
if (!message)
|
|
251
|
-
continue;
|
|
252
|
-
if (message.role === "user")
|
|
253
|
-
return undefined;
|
|
254
|
-
if (message.role === "toolResult")
|
|
255
|
-
continue;
|
|
256
|
-
if (message.role === "assistant") {
|
|
257
|
-
const text = extractAssistantText(message).trim();
|
|
258
|
-
if (!text)
|
|
259
|
-
continue;
|
|
260
|
-
if (opts?.skipLastAssistant) {
|
|
261
|
-
assistantFromEnd += 1;
|
|
262
|
-
if (assistantFromEnd === 1)
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
return text;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return undefined;
|
|
269
|
-
}
|
|
270
|
-
function shouldSuppressEntireAssistantMessage(message, sessionMessages, orphaned) {
|
|
271
|
-
const textBlocks = (message.content ?? []).filter((block) => block?.type === "text" && typeof block.text === "string" && block.text.trim());
|
|
272
|
-
if (textBlocks.length !== 1)
|
|
273
|
-
return false;
|
|
274
|
-
return shouldSuppressRedundantHandoffText(sessionMessages, textBlocks[0].text, orphaned, []);
|
|
275
|
-
}
|
|
276
|
-
function shouldSuppressRedundantHandoffText(sessionMessages, currentText, orphaned, rendered) {
|
|
277
|
-
const next = currentText.trim();
|
|
278
|
-
if (!next)
|
|
279
|
-
return false;
|
|
280
|
-
const priorInline = latestPriorUserFacingText(orphaned, rendered);
|
|
281
|
-
if (priorInline && isRedundantDiscussRestatement(priorInline, next)) {
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
const last = sessionMessages[sessionMessages.length - 1];
|
|
285
|
-
const skipLastAssistant = last?.role === "assistant" && extractAssistantText(last).trim() === next;
|
|
286
|
-
const priorSession = priorAssistantTextFromSession(sessionMessages, { skipLastAssistant });
|
|
287
|
-
return !!(priorSession && isRedundantDiscussRestatement(priorSession, next));
|
|
288
|
-
}
|
|
289
|
-
function buildDesiredSegments(blocks, options = {}) {
|
|
290
|
-
const desired = [];
|
|
291
|
-
let runStart = -1;
|
|
292
|
-
let runEnd = -1;
|
|
293
|
-
let runType;
|
|
294
|
-
const closeRun = () => {
|
|
295
|
-
if (runStart !== -1 && runType) {
|
|
296
|
-
desired.push({ kind: "text-run", startIndex: runStart, endIndex: runEnd, contentType: runType });
|
|
297
|
-
runStart = -1;
|
|
298
|
-
runEnd = -1;
|
|
299
|
-
runType = undefined;
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
303
|
-
const block = blocks[i];
|
|
304
|
-
const blockType = getVisibleTextLikeBlockType(block, options.hideThinkingBlock);
|
|
305
|
-
const isInvisibleTextLike = blockType === undefined && (block?.type === "text" || block?.type === "thinking");
|
|
306
|
-
const isTool = block?.type === "toolCall" || block?.type === "serverToolUse";
|
|
307
|
-
if (blockType) {
|
|
308
|
-
if (options.shouldSkipTextBlock?.(block, i)) {
|
|
309
|
-
closeRun();
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (runStart === -1) {
|
|
313
|
-
runStart = i;
|
|
314
|
-
runEnd = i;
|
|
315
|
-
runType = blockType;
|
|
316
|
-
}
|
|
317
|
-
else if (runType !== blockType) {
|
|
318
|
-
closeRun();
|
|
319
|
-
runStart = i;
|
|
320
|
-
runEnd = i;
|
|
321
|
-
runType = blockType;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
runEnd = i;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
if (isInvisibleTextLike)
|
|
329
|
-
continue;
|
|
330
|
-
closeRun();
|
|
331
|
-
if (isTool) {
|
|
332
|
-
desired.push({ kind: "tool", contentIndex: i, toolId: block.id });
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
closeRun();
|
|
337
|
-
return desired;
|
|
338
|
-
}
|
|
339
|
-
function isToolUseBlock(block) {
|
|
340
|
-
return block?.type === "toolCall" || block?.type === "serverToolUse";
|
|
341
|
-
}
|
|
342
|
-
function isMcpToolBlock(block) {
|
|
343
|
-
if (!isToolUseBlock(block))
|
|
344
|
-
return false;
|
|
345
|
-
const toolName = typeof block?.name === "string" ? block.name : "";
|
|
346
|
-
return typeof block?.mcpServer === "string" || toolName.startsWith("mcp__");
|
|
347
|
-
}
|
|
348
|
-
function hasPostToolText(blocks, firstToolIdx) {
|
|
349
|
-
if (firstToolIdx < 0)
|
|
350
|
-
return false;
|
|
351
|
-
return blocks.some((b, idx) => (idx > firstToolIdx
|
|
352
|
-
&& b?.type === "text"
|
|
353
|
-
&& typeof b?.text === "string"
|
|
354
|
-
&& b.text.trim().length > 0));
|
|
355
|
-
}
|
|
356
|
-
function normalizeProvisionalText(text) {
|
|
357
|
-
return text
|
|
358
|
-
.trim()
|
|
359
|
-
.replace(/[’`]/g, "'")
|
|
360
|
-
.replace(/\s+/g, " ");
|
|
361
|
-
}
|
|
362
|
-
export function isProvisionalPreToolProse(text) {
|
|
363
|
-
const normalized = normalizeProvisionalText(text);
|
|
364
|
-
if (!normalized)
|
|
365
|
-
return false;
|
|
366
|
-
if (textInvitesUserReply(normalized))
|
|
367
|
-
return false;
|
|
368
|
-
if (/\?\s*$/.test(normalized))
|
|
369
|
-
return false;
|
|
370
|
-
return FIRST_PERSON_PROVISIONAL_PRE_TOOL_RE.test(normalized)
|
|
371
|
-
|| GERUND_PROVISIONAL_PRE_TOOL_RE.test(normalized);
|
|
372
|
-
}
|
|
373
|
-
function getProvisionalPreToolPrunePlan(message) {
|
|
374
|
-
const blocks = message.content;
|
|
375
|
-
const firstToolIdx = blocks.findIndex(isToolUseBlock);
|
|
376
|
-
return {
|
|
377
|
-
firstToolIdx,
|
|
378
|
-
shouldPrune: message.provider === "claude-code"
|
|
379
|
-
&& firstToolIdx >= 0
|
|
380
|
-
&& blocks.some(isMcpToolBlock)
|
|
381
|
-
&& hasPostToolText(blocks, firstToolIdx),
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
function buildDesiredSegmentsForMessage(message, options = {}) {
|
|
385
|
-
const { shouldPrune, firstToolIdx } = getProvisionalPreToolPrunePlan(message);
|
|
386
|
-
return buildDesiredSegments(message.content, {
|
|
387
|
-
hideThinkingBlock: options.hideThinkingBlock,
|
|
388
|
-
shouldSkipTextBlock: (block, index) => {
|
|
389
|
-
if (!shouldPrune || firstToolIdx < 0 || index >= firstToolIdx)
|
|
390
|
-
return false;
|
|
391
|
-
if (getVisibleTextLikeBlockType(block, options.hideThinkingBlock) !== "text")
|
|
392
|
-
return false;
|
|
393
|
-
const textValue = typeof block?.text === "string" ? block.text : "";
|
|
394
|
-
return isProvisionalPreToolProse(textValue);
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
function hasVisibleAssistantContent(message, hideThinkingBlock = false) {
|
|
399
|
-
return message.content.some((c) => getVisibleTextLikeBlockType(c, hideThinkingBlock) !== undefined);
|
|
400
|
-
}
|
|
401
|
-
function hasAssistantToolBlocks(message) {
|
|
402
|
-
return message.content.some((c) => c.type === "toolCall" || c.type === "serverToolUse");
|
|
403
|
-
}
|
|
404
|
-
// Pinnable text candidates: non-empty text blocks that appear strictly before
|
|
405
|
-
// the most recent tool call, returned newest-first. Text blocks after the last
|
|
406
|
-
// tool call are still streaming live into the chat container.
|
|
407
|
-
export function findLatestPinnableCandidates(contentBlocks) {
|
|
408
|
-
let lastToolIdx = -1;
|
|
409
|
-
for (let i = contentBlocks.length - 1; i >= 0; i--) {
|
|
410
|
-
const c = contentBlocks[i];
|
|
411
|
-
if (c?.type === "toolCall" || c?.type === "serverToolUse") {
|
|
412
|
-
lastToolIdx = i;
|
|
413
|
-
break;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
const out = [];
|
|
417
|
-
for (let i = lastToolIdx - 1; i >= 0; i--) {
|
|
418
|
-
const c = contentBlocks[i];
|
|
419
|
-
if (c?.type === "text" && typeof c.text === "string" && c.text.trim()) {
|
|
420
|
-
out.push({ text: c.text.trim(), contentIndex: i });
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return out;
|
|
424
|
-
}
|
|
425
|
-
export function findLatestPinnableText(contentBlocks) {
|
|
426
|
-
return findLatestPinnableCandidates(contentBlocks)[0]?.text ?? "";
|
|
427
|
-
}
|
|
428
|
-
// Sum rendered line counts of segments that appear strictly after the given
|
|
429
|
-
// content-block index. Used to decide whether a pinnable text block has
|
|
430
|
-
// scrolled out of the viewport and therefore warrants mirroring.
|
|
431
|
-
function rowsRenderedAfterContentIndex(contentIndex, width) {
|
|
432
|
-
let rows = 0;
|
|
433
|
-
for (const seg of renderedSegments) {
|
|
434
|
-
try {
|
|
435
|
-
if (seg.kind === "text-run" && seg.startIndex > contentIndex) {
|
|
436
|
-
rows += seg.component.render(width).length;
|
|
437
|
-
}
|
|
438
|
-
else if (seg.kind === "tool" && seg.contentIndex > contentIndex) {
|
|
439
|
-
rows += seg.component.render(width).length;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
catch {
|
|
443
|
-
// Defensive: a component that throws during measurement shouldn't
|
|
444
|
-
// destabilize pinned-zone logic. Skip it.
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return rows;
|
|
448
|
-
}
|
|
449
|
-
// Tracks the latest assistant text for the pinned message zone
|
|
450
|
-
let lastPinnedText = "";
|
|
451
|
-
// Whether any tool execution has been added in this assistant turn (triggers pinned display)
|
|
452
|
-
let hasToolsInTurn = false;
|
|
453
|
-
// Reference to the pinned border so we can toggle its label between working/idle
|
|
454
|
-
let pinnedBorder;
|
|
455
|
-
// Reference to the pinned markdown component below the border
|
|
456
|
-
let pinnedTextComponent;
|
|
457
|
-
// Set when the pinned zone was shown this turn; used to realign viewport after teardown.
|
|
458
|
-
let pinnedZoneNeedsViewportRealign = false;
|
|
459
|
-
function tearDownPinnedZone(host, options) {
|
|
460
|
-
const needsRealign = pinnedZoneNeedsViewportRealign;
|
|
461
|
-
if (pinnedBorder)
|
|
462
|
-
pinnedBorder.stopSpinner();
|
|
463
|
-
pinnedBorder = undefined;
|
|
464
|
-
pinnedTextComponent = undefined;
|
|
465
|
-
host.pinnedMessageContainer.clear();
|
|
466
|
-
lastPinnedText = "";
|
|
467
|
-
hasToolsInTurn = false;
|
|
468
|
-
pinnedZoneNeedsViewportRealign = false;
|
|
469
|
-
if (options?.realignViewport && needsRealign) {
|
|
470
|
-
host.ui.requestRender(true);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
function mergeToolPhases(phases) {
|
|
474
|
-
const merged = [];
|
|
475
|
-
for (const phase of phases) {
|
|
476
|
-
const previous = merged[merged.length - 1];
|
|
477
|
-
if (previous?.label === phase.label) {
|
|
478
|
-
previous.count += phase.count;
|
|
479
|
-
previous.durationMs += phase.durationMs;
|
|
480
|
-
previous.targets = mergeTargets(previous.targets, phase.targets);
|
|
481
|
-
if (previous.actionLabel !== phase.actionLabel) {
|
|
482
|
-
previous.actionLabel = undefined;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
merged.push({ ...phase, targets: phase.targets ? [...phase.targets] : undefined });
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
return merged;
|
|
490
|
-
}
|
|
491
|
-
function mergeTargets(existing, incoming) {
|
|
492
|
-
if (!existing && !incoming)
|
|
493
|
-
return undefined;
|
|
494
|
-
const seen = new Set();
|
|
495
|
-
const merged = [];
|
|
496
|
-
for (const target of [...(existing ?? []), ...(incoming ?? [])]) {
|
|
497
|
-
if (!target || seen.has(target))
|
|
498
|
-
continue;
|
|
499
|
-
seen.add(target);
|
|
500
|
-
merged.push(target);
|
|
501
|
-
}
|
|
502
|
-
return merged;
|
|
503
|
-
}
|
|
504
|
-
function replaceCompactToolRowsWithPhaseSummary(host) {
|
|
505
|
-
let changed = false;
|
|
506
|
-
const nextRenderedSegments = [];
|
|
507
|
-
let rollupRun = [];
|
|
508
|
-
const flushRollupRun = () => {
|
|
509
|
-
const actionCount = rollupRun.reduce((total, item) => total + item.phases.reduce((sum, phase) => sum + phase.count, 0), 0);
|
|
510
|
-
if (actionCount < 2) {
|
|
511
|
-
nextRenderedSegments.push(...rollupRun.map((item) => item.seg));
|
|
512
|
-
rollupRun = [];
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
const firstIndex = Math.max(0, host.chatContainer.children.indexOf(rollupRun[0].seg.component));
|
|
516
|
-
const phases = mergeToolPhases(rollupRun.flatMap((item) => item.phases));
|
|
517
|
-
const summary = new ToolPhaseSummaryComponent(phases);
|
|
518
|
-
for (const { seg } of rollupRun) {
|
|
519
|
-
host.chatContainer.removeChild(seg.component);
|
|
520
|
-
}
|
|
521
|
-
host.chatContainer.addChild(summary);
|
|
522
|
-
const summaryIndex = host.chatContainer.children.indexOf(summary);
|
|
523
|
-
if (summaryIndex !== -1 && summaryIndex !== firstIndex) {
|
|
524
|
-
host.chatContainer.children.splice(summaryIndex, 1);
|
|
525
|
-
host.chatContainer.children.splice(firstIndex, 0, summary);
|
|
526
|
-
host.chatContainer._prevRender = null;
|
|
527
|
-
}
|
|
528
|
-
changed = true;
|
|
529
|
-
nextRenderedSegments.push({ kind: "tool-summary", component: summary, phases });
|
|
530
|
-
rollupRun = [];
|
|
531
|
-
};
|
|
532
|
-
for (const seg of renderedSegments) {
|
|
533
|
-
const phase = seg.kind === "tool" ? seg.component.getRollupPhase() : null;
|
|
534
|
-
if (seg.kind === "tool" && phase) {
|
|
535
|
-
rollupRun.push({ seg, phases: [phase] });
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
if (seg.kind === "tool-summary") {
|
|
539
|
-
rollupRun.push({ seg, phases: seg.component.getPhases() });
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
flushRollupRun();
|
|
543
|
-
nextRenderedSegments.push(seg);
|
|
544
|
-
}
|
|
545
|
-
flushRollupRun();
|
|
546
|
-
if (changed) {
|
|
547
|
-
renderedSegments = nextRenderedSegments;
|
|
548
|
-
host.ui.requestRender();
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
50
|
export async function handleAgentEvent(host, event) {
|
|
552
51
|
if (!host.isInitialized) {
|
|
553
52
|
await host.init();
|
|
554
53
|
}
|
|
555
54
|
host.footer.invalidate();
|
|
556
55
|
const timestampFormat = host.settingsManager.getTimestampFormat();
|
|
56
|
+
const rs = host.streamingRenderState;
|
|
557
57
|
// Reset content index tracker and pinned state when a new assistant message starts
|
|
558
58
|
if (event.type === "message_start" && event.message.role === "assistant") {
|
|
559
|
-
|
|
560
|
-
lastContentLength = 0;
|
|
561
|
-
renderedSegments = [];
|
|
562
|
-
orphanedSegments = [];
|
|
59
|
+
rs.resetForNewAssistantMessage();
|
|
563
60
|
tearDownPinnedZone(host);
|
|
564
61
|
}
|
|
565
62
|
switch (event.type) {
|
|
@@ -573,9 +70,7 @@ export async function handleAgentEvent(host, event) {
|
|
|
573
70
|
host.pendingTools.clear();
|
|
574
71
|
host.pendingMessagesContainer.clear();
|
|
575
72
|
tearDownPinnedZone(host);
|
|
576
|
-
|
|
577
|
-
orphanedSegments = [];
|
|
578
|
-
lastContentLength = 0;
|
|
73
|
+
rs.resetForSessionChange();
|
|
579
74
|
host.compactionQueuedMessages = [];
|
|
580
75
|
host.rebuildChatFromMessages();
|
|
581
76
|
host.updatePendingMessagesDisplay();
|
|
@@ -675,68 +170,12 @@ export async function handleAgentEvent(host, event) {
|
|
|
675
170
|
// lifecycle while internally spanning multiple provider sub-turns.
|
|
676
171
|
// When a new sub-turn starts, content[] length shrinks back to 0/1.
|
|
677
172
|
// The scan loop needs its index reset, AND the segment walker's
|
|
678
|
-
// renderedSegments map must be cleared so existing text-run
|
|
173
|
+
// rs.renderedSegments map must be cleared so existing text-run
|
|
679
174
|
// components don't get overwritten in place with new sub-turn
|
|
680
175
|
// content (#4144 regression). Prior sub-turn children stay in
|
|
681
176
|
// chatContainer as frozen history; new segments append after them.
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
: null;
|
|
685
|
-
if (contentBlocks.length < lastContentLength) {
|
|
686
|
-
// Accumulate across successive shrinks — overwriting would drop
|
|
687
|
-
// segments displaced by an earlier shrink, leaving them stranded
|
|
688
|
-
// in chatContainer once the prune pass finally runs.
|
|
689
|
-
orphanedSegments = [...orphanedSegments, ...renderedSegments];
|
|
690
|
-
renderedSegments = [];
|
|
691
|
-
lastPinnedText = "";
|
|
692
|
-
lastProcessedContentIndex = 0;
|
|
693
|
-
}
|
|
694
|
-
else if (replacedAt !== null) {
|
|
695
|
-
// Same-index wholesale replacement: orphan only the replaced
|
|
696
|
-
// text-run and any text-runs after it. Earlier unchanged text
|
|
697
|
-
// and tool segments stay in renderedSegments so they are not
|
|
698
|
-
// re-rendered and duplicated in chatContainer.
|
|
699
|
-
orphanedSegments = [
|
|
700
|
-
...orphanedSegments,
|
|
701
|
-
...renderedSegments.filter((seg) => seg.kind === "text-run" && seg.startIndex >= replacedAt),
|
|
702
|
-
];
|
|
703
|
-
renderedSegments = renderedSegments.filter((seg) => !(seg.kind === "text-run" && seg.startIndex >= replacedAt));
|
|
704
|
-
lastPinnedText = "";
|
|
705
|
-
lastProcessedContentIndex = replacedAt;
|
|
706
|
-
}
|
|
707
|
-
else if (lastProcessedContentIndex >= contentBlocks.length) {
|
|
708
|
-
lastProcessedContentIndex = 0;
|
|
709
|
-
}
|
|
710
|
-
lastContentLength = contentBlocks.length;
|
|
711
|
-
for (let i = lastProcessedContentIndex; i < contentBlocks.length; i++) {
|
|
712
|
-
const content = contentBlocks[i];
|
|
713
|
-
if (content.type === "toolCall") {
|
|
714
|
-
const { component } = registerPendingToolComponent(host, content.id, content.name, content.arguments, "content", () => new ToolExecutionComponent(content.name, content.arguments, { showImages: host.settingsManager.getShowImages() }, host.getRegisteredToolDefinition(content.name), host.ui));
|
|
715
|
-
component.updateArgs(content.arguments);
|
|
716
|
-
}
|
|
717
|
-
else if (content.type === "serverToolUse") {
|
|
718
|
-
registerPendingToolComponent(host, content.id, content.name, content.input ?? {}, "content", () => new ToolExecutionComponent(content.name, content.input ?? {}, { showImages: host.settingsManager.getShowImages() }, undefined, host.ui));
|
|
719
|
-
}
|
|
720
|
-
else if (content.type === "webSearchResult") {
|
|
721
|
-
const component = host.pendingTools.get(content.toolUseId);
|
|
722
|
-
if (component) {
|
|
723
|
-
if (process.env.PI_OFFLINE === "1") {
|
|
724
|
-
component.updateResult({
|
|
725
|
-
content: [{ type: "text", text: "Web search disabled (offline mode)" }],
|
|
726
|
-
isError: false,
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
const searchContent = content.content;
|
|
731
|
-
const isError = searchContent && typeof searchContent === "object" && "type" in searchContent && searchContent.type === "web_search_tool_result_error";
|
|
732
|
-
component.updateResult({
|
|
733
|
-
content: [{ type: "text", text: host.formatWebSearchResult(searchContent) }],
|
|
734
|
-
isError: !!isError,
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
177
|
+
applySubTurnContentShrink(rs, contentBlocks);
|
|
178
|
+
scanNewContentBlocks(host, rs, contentBlocks);
|
|
740
179
|
// When the stream adapter signals a completed tool call with an
|
|
741
180
|
// external result (from Claude Code SDK), update the pending
|
|
742
181
|
// ToolExecutionComponent immediately so output is visible in
|
|
@@ -752,189 +191,19 @@ export async function handleAgentEvent(host, event) {
|
|
|
752
191
|
replaceCompactToolRowsWithPhaseSummary(host);
|
|
753
192
|
}
|
|
754
193
|
}
|
|
755
|
-
|
|
756
|
-
// Build desired segment plan from content[].
|
|
757
|
-
{
|
|
758
|
-
const blocks = host.streamingMessage.content;
|
|
759
|
-
// Only prune provisional pre-tool prose after post-tool prose exists,
|
|
760
|
-
// so MCP tool-only windows do not blank the assistant content.
|
|
761
|
-
const { shouldPrune: shouldPruneProvisionalPreToolProse } = getProvisionalPreToolPrunePlan(host.streamingMessage);
|
|
762
|
-
let desired = buildDesiredSegmentsForMessage(host.streamingMessage, {
|
|
763
|
-
hideThinkingBlock: host.hideThinkingBlock,
|
|
764
|
-
});
|
|
765
|
-
desired = filterRedundantDiscussTextRuns(desired, blocks);
|
|
766
|
-
// Claude Code MCP can emit provisional pre-tool prose that gets
|
|
767
|
-
// superseded by post-tool output. Prune stale text-run segments so
|
|
768
|
-
// the final assistant output remains below tool output.
|
|
769
|
-
if (shouldPruneProvisionalPreToolProse) {
|
|
770
|
-
if (orphanedSegments.length > 0) {
|
|
771
|
-
const remainingOrphans = [];
|
|
772
|
-
for (const orphan of orphanedSegments) {
|
|
773
|
-
if (orphan.kind === "text-run"
|
|
774
|
-
&& orphan.contentType === "text"
|
|
775
|
-
&& isProvisionalPreToolProse(orphan.cachedText ?? "")) {
|
|
776
|
-
host.chatContainer.removeChild(orphan.component);
|
|
777
|
-
if (host.streamingComponent === orphan.component) {
|
|
778
|
-
host.streamingComponent = undefined;
|
|
779
|
-
}
|
|
780
|
-
continue;
|
|
781
|
-
}
|
|
782
|
-
remainingOrphans.push(orphan);
|
|
783
|
-
}
|
|
784
|
-
orphanedSegments = remainingOrphans;
|
|
785
|
-
}
|
|
786
|
-
const desiredTextKeys = new Set(desired
|
|
787
|
-
.filter((seg) => seg.kind === "text-run")
|
|
788
|
-
.map((seg) => `${seg.contentType}:${seg.startIndex}`));
|
|
789
|
-
const desiredToolIndices = new Set(desired
|
|
790
|
-
.filter((seg) => seg.kind === "tool")
|
|
791
|
-
.map((seg) => seg.contentIndex));
|
|
792
|
-
const nextRendered = [];
|
|
793
|
-
for (const seg of renderedSegments) {
|
|
794
|
-
if (seg.kind === "text-run"
|
|
795
|
-
&& seg.contentType === "text"
|
|
796
|
-
&& !desiredTextKeys.has(`${seg.contentType}:${seg.startIndex}`)) {
|
|
797
|
-
host.chatContainer.removeChild(seg.component);
|
|
798
|
-
if (host.streamingComponent === seg.component) {
|
|
799
|
-
host.streamingComponent = undefined;
|
|
800
|
-
}
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
if (seg.kind === "tool" && !desiredToolIndices.has(seg.contentIndex)) {
|
|
804
|
-
continue;
|
|
805
|
-
}
|
|
806
|
-
nextRendered.push(seg);
|
|
807
|
-
}
|
|
808
|
-
renderedSegments = nextRendered;
|
|
809
|
-
}
|
|
810
|
-
// Append any newly needed segments (never reorder existing ones).
|
|
811
|
-
for (const seg of desired) {
|
|
812
|
-
if (seg.kind === "tool") {
|
|
813
|
-
// Tool segments are already handled above via pendingTools; just
|
|
814
|
-
// register them in renderedSegments if not yet tracked.
|
|
815
|
-
const existing = renderedSegments.find((s) => s.kind === "tool" && s.contentIndex === seg.contentIndex);
|
|
816
|
-
if (!existing) {
|
|
817
|
-
const comp = host.pendingTools.get(seg.toolId);
|
|
818
|
-
if (comp) {
|
|
819
|
-
renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component: comp });
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
else {
|
|
824
|
-
// text-run segment
|
|
825
|
-
const existing = renderedSegments.find((s) => s.kind === "text-run" && s.startIndex === seg.startIndex && s.contentType === seg.contentType);
|
|
826
|
-
if (!existing) {
|
|
827
|
-
const segmentText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex);
|
|
828
|
-
if (shouldSuppressRedundantHandoffText(host.session.messages, segmentText, orphanedSegments, renderedSegments)) {
|
|
829
|
-
continue;
|
|
830
|
-
}
|
|
831
|
-
const comp = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, { startIndex: seg.startIndex, endIndex: seg.endIndex });
|
|
832
|
-
host.chatContainer.addChild(comp);
|
|
833
|
-
markFirstVisibleAssistantOutput(host, seg.contentType, {
|
|
834
|
-
contentIndex: seg.startIndex,
|
|
835
|
-
});
|
|
836
|
-
renderedSegments.push({
|
|
837
|
-
kind: "text-run",
|
|
838
|
-
startIndex: seg.startIndex,
|
|
839
|
-
endIndex: seg.endIndex,
|
|
840
|
-
contentType: seg.contentType,
|
|
841
|
-
component: comp,
|
|
842
|
-
cachedText: segmentText,
|
|
843
|
-
});
|
|
844
|
-
host.streamingComponent = comp;
|
|
845
|
-
reconcileChatTurnConnections(host.chatContainer.children);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
// Update all trailing text-run segments with the latest message so
|
|
850
|
-
// streaming text grows in place.
|
|
851
|
-
for (const seg of renderedSegments) {
|
|
852
|
-
if (seg.kind === "text-run") {
|
|
853
|
-
// Find corresponding desired segment to get current endIndex
|
|
854
|
-
const d = desired.find((ds) => ds.kind === "text-run" && ds.startIndex === seg.startIndex && ds.contentType === seg.contentType);
|
|
855
|
-
if (d && d.kind === "text-run" && d.endIndex !== seg.endIndex) {
|
|
856
|
-
seg.endIndex = d.endIndex;
|
|
857
|
-
seg.component.setRange({ startIndex: seg.startIndex, endIndex: seg.endIndex });
|
|
858
|
-
}
|
|
859
|
-
seg.cachedText = getTextFromContentBlocks(blocks, seg.startIndex, seg.endIndex);
|
|
860
|
-
seg.component.updateContent(host.streamingMessage);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
// Keep streamingComponent pointing at the last text-run for message_end compatibility.
|
|
864
|
-
const lastTextSeg = [...renderedSegments].reverse().find((s) => s.kind === "text-run");
|
|
865
|
-
if (lastTextSeg && lastTextSeg.kind === "text-run") {
|
|
866
|
-
host.streamingComponent = lastTextSeg.component;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
194
|
+
runSegmentWalker(host, rs, timestampFormat);
|
|
869
195
|
// Update index: fully processed blocks won't need re-scanning.
|
|
870
196
|
// Keep the last block's index (it may still be accumulating data),
|
|
871
197
|
// so we re-check it next time but skip all earlier ones.
|
|
872
198
|
if (contentBlocks.length > 0) {
|
|
873
|
-
lastProcessedContentIndex = Math.max(0, contentBlocks.length - 1);
|
|
199
|
+
rs.lastProcessedContentIndex = Math.max(0, contentBlocks.length - 1);
|
|
874
200
|
}
|
|
875
201
|
// Pinned message: mirror the latest assistant text above the editor
|
|
876
202
|
// when tool executions push it out of the viewport.
|
|
877
|
-
const
|
|
878
|
-
if (
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
const candidates = findLatestPinnableCandidates(contentBlocks);
|
|
882
|
-
const termRows = host.ui.terminal.rows;
|
|
883
|
-
const termCols = host.ui.terminal.columns;
|
|
884
|
-
const pinnedMax = Math.max(3, Math.floor(termRows * 0.4));
|
|
885
|
-
// Reserve rows for pinned zone + its border + editor + footer chrome.
|
|
886
|
-
// Anything below this row budget is still in the viewport.
|
|
887
|
-
const offscreenThreshold = Math.max(1, termRows - pinnedMax - 8);
|
|
888
|
-
// Walk candidates newest→oldest; pick the first whose following
|
|
889
|
-
// segments have pushed enough rows to scroll it off-screen.
|
|
890
|
-
let picked;
|
|
891
|
-
for (const c of candidates) {
|
|
892
|
-
if (rowsRenderedAfterContentIndex(c.contentIndex, termCols) >= offscreenThreshold) {
|
|
893
|
-
picked = c;
|
|
894
|
-
break;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
if (picked) {
|
|
898
|
-
if (picked.text !== lastPinnedText) {
|
|
899
|
-
lastPinnedText = picked.text;
|
|
900
|
-
if (!pinnedBorder) {
|
|
901
|
-
// First time: create border + text component
|
|
902
|
-
host.pinnedMessageContainer.clear();
|
|
903
|
-
pinnedBorder = new DynamicBorder((str) => theme.fg("dim", str), "Working · Latest Output");
|
|
904
|
-
pinnedBorder.startSpinner(host.ui, (str) => theme.fg("accent", str));
|
|
905
|
-
host.pinnedMessageContainer.addChild(pinnedBorder);
|
|
906
|
-
pinnedTextComponent = new Markdown(picked.text, 1, 0, host.getMarkdownThemeWithSettings());
|
|
907
|
-
// Cap pinned content to ~40% of terminal height so tall output
|
|
908
|
-
// doesn't exceed the viewport and cause render flashing.
|
|
909
|
-
pinnedTextComponent.maxLines = pinnedMax;
|
|
910
|
-
host.pinnedMessageContainer.addChild(pinnedTextComponent);
|
|
911
|
-
pinnedZoneNeedsViewportRealign = true;
|
|
912
|
-
// Hide the separate status loader — the pinned zone replaces it
|
|
913
|
-
if (host.loadingAnimation) {
|
|
914
|
-
host.loadingAnimation.stop();
|
|
915
|
-
host.loadingAnimation = undefined;
|
|
916
|
-
}
|
|
917
|
-
host.statusContainer.clear();
|
|
918
|
-
}
|
|
919
|
-
else {
|
|
920
|
-
// Update existing markdown component in-place
|
|
921
|
-
pinnedTextComponent?.setText(picked.text);
|
|
922
|
-
// Refresh maxLines in case terminal was resized
|
|
923
|
-
if (pinnedTextComponent) {
|
|
924
|
-
pinnedTextComponent.maxLines = pinnedMax;
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
else if (pinnedBorder) {
|
|
930
|
-
// Every candidate is still visible in the chat scrollback —
|
|
931
|
-
// tear down the pinned zone so we don't duplicate on-screen text.
|
|
932
|
-
tearDownPinnedZone(host);
|
|
933
|
-
if (!host.loadingAnimation) {
|
|
934
|
-
host.statusContainer.clear();
|
|
935
|
-
startLoadingAnimation(host);
|
|
936
|
-
}
|
|
937
|
-
}
|
|
203
|
+
const { toreDownPinnedZone } = updatePinnedMessageZone(host, rs, contentBlocks);
|
|
204
|
+
if (toreDownPinnedZone && !host.loadingAnimation) {
|
|
205
|
+
host.statusContainer.clear();
|
|
206
|
+
startLoadingAnimation(host);
|
|
938
207
|
}
|
|
939
208
|
host.ui.requestRender();
|
|
940
209
|
}
|
|
@@ -955,80 +224,12 @@ export async function handleAgentEvent(host, event) {
|
|
|
955
224
|
const shouldRenderAssistant = hasVisibleAssistantContent(host.streamingMessage, host.hideThinkingBlock) ||
|
|
956
225
|
((host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error") &&
|
|
957
226
|
!hasAssistantToolBlocks(host.streamingMessage));
|
|
958
|
-
const suppressRedundantHandoff = shouldSuppressEntireAssistantMessage(host.streamingMessage, host.session.messages, orphanedSegments);
|
|
227
|
+
const suppressRedundantHandoff = shouldSuppressEntireAssistantMessage(host.streamingMessage, host.session.messages, rs.orphanedSegments);
|
|
959
228
|
// The final message_end payload can contain additional text/thinking
|
|
960
229
|
// blocks that never arrived via message_update (e.g. SDK result
|
|
961
230
|
// aggregation). Rebuild this in-flight turn from final content so
|
|
962
231
|
// ranges/components don't keep stale partial indices.
|
|
963
|
-
|
|
964
|
-
const finalBlocks = host.streamingMessage.content;
|
|
965
|
-
const desired = filterRedundantDiscussTextRuns(buildDesiredSegmentsForMessage(host.streamingMessage, {
|
|
966
|
-
hideThinkingBlock: host.hideThinkingBlock,
|
|
967
|
-
}), finalBlocks);
|
|
968
|
-
const toolComponentsById = new Map();
|
|
969
|
-
for (const [toolId, component] of host.pendingTools.entries()) {
|
|
970
|
-
toolComponentsById.set(toolId, component);
|
|
971
|
-
}
|
|
972
|
-
for (const seg of renderedSegments) {
|
|
973
|
-
host.chatContainer.removeChild(seg.component);
|
|
974
|
-
if (seg.kind === "tool") {
|
|
975
|
-
const priorBlocks = host.streamingMessage.content;
|
|
976
|
-
const priorBlock = priorBlocks[seg.contentIndex];
|
|
977
|
-
if (priorBlock?.id && !toolComponentsById.has(priorBlock.id)) {
|
|
978
|
-
toolComponentsById.set(priorBlock.id, seg.component);
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
renderedSegments = [];
|
|
983
|
-
host.streamingComponent = undefined;
|
|
984
|
-
for (const seg of desired) {
|
|
985
|
-
if (seg.kind === "tool") {
|
|
986
|
-
const finalBlock = finalBlocks[seg.contentIndex];
|
|
987
|
-
let component = toolComponentsById.get(seg.toolId);
|
|
988
|
-
if (!component && finalBlock?.id) {
|
|
989
|
-
component = host.pendingTools.get(finalBlock.id);
|
|
990
|
-
}
|
|
991
|
-
if (!component && finalBlock?.type === "toolCall") {
|
|
992
|
-
component = new ToolExecutionComponent(finalBlock.name, finalBlock.arguments, { showImages: host.settingsManager.getShowImages() }, host.getRegisteredToolDefinition(finalBlock.name), host.ui);
|
|
993
|
-
component.setExpanded(host.toolOutputExpanded);
|
|
994
|
-
host.pendingTools.set(finalBlock.id, component);
|
|
995
|
-
toolComponentsById.set(finalBlock.id, component);
|
|
996
|
-
}
|
|
997
|
-
else if (!component && finalBlock?.type === "serverToolUse") {
|
|
998
|
-
component = new ToolExecutionComponent(finalBlock.name, finalBlock.input ?? {}, { showImages: host.settingsManager.getShowImages() }, undefined, host.ui);
|
|
999
|
-
component.setExpanded(host.toolOutputExpanded);
|
|
1000
|
-
host.pendingTools.set(finalBlock.id, component);
|
|
1001
|
-
toolComponentsById.set(finalBlock.id, component);
|
|
1002
|
-
}
|
|
1003
|
-
if (component) {
|
|
1004
|
-
host.chatContainer.addChild(component);
|
|
1005
|
-
renderedSegments.push({ kind: "tool", contentIndex: seg.contentIndex, component });
|
|
1006
|
-
}
|
|
1007
|
-
continue;
|
|
1008
|
-
}
|
|
1009
|
-
const comp = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, { startIndex: seg.startIndex, endIndex: seg.endIndex });
|
|
1010
|
-
comp.updateContent(host.streamingMessage);
|
|
1011
|
-
const segmentText = getTextFromContentBlocks(finalBlocks, seg.startIndex, seg.endIndex);
|
|
1012
|
-
if (shouldSuppressRedundantHandoffText(host.session.messages, segmentText, orphanedSegments, renderedSegments)) {
|
|
1013
|
-
continue;
|
|
1014
|
-
}
|
|
1015
|
-
host.chatContainer.addChild(comp);
|
|
1016
|
-
markFirstVisibleAssistantOutput(host, seg.contentType, {
|
|
1017
|
-
contentIndex: seg.startIndex,
|
|
1018
|
-
source: "message_end_rebuild",
|
|
1019
|
-
});
|
|
1020
|
-
renderedSegments.push({
|
|
1021
|
-
kind: "text-run",
|
|
1022
|
-
startIndex: seg.startIndex,
|
|
1023
|
-
endIndex: seg.endIndex,
|
|
1024
|
-
contentType: seg.contentType,
|
|
1025
|
-
component: comp,
|
|
1026
|
-
cachedText: segmentText,
|
|
1027
|
-
});
|
|
1028
|
-
host.streamingComponent = comp;
|
|
1029
|
-
}
|
|
1030
|
-
reconcileChatTurnConnections(host.chatContainer.children);
|
|
1031
|
-
}
|
|
232
|
+
rebuildSegmentsOnMessageEnd(host, rs, timestampFormat);
|
|
1032
233
|
if (!host.streamingComponent && shouldRenderAssistant && !suppressRedundantHandoff) {
|
|
1033
234
|
host.streamingComponent = new AssistantMessageComponent(undefined, host.hideThinkingBlock, host.getMarkdownThemeWithSettings(), timestampFormat, undefined);
|
|
1034
235
|
host.chatContainer.addChild(host.streamingComponent);
|
|
@@ -1061,9 +262,7 @@ export async function handleAgentEvent(host, event) {
|
|
|
1061
262
|
}
|
|
1062
263
|
host.streamingComponent = undefined;
|
|
1063
264
|
host.streamingMessage = undefined;
|
|
1064
|
-
|
|
1065
|
-
orphanedSegments = [];
|
|
1066
|
-
lastContentLength = 0;
|
|
265
|
+
rs.resetStreamingSegments();
|
|
1067
266
|
// Clear pinned output once the message is finalized in the chat
|
|
1068
267
|
// container — prevents duplicate display when the agent continues
|
|
1069
268
|
// (e.g. form elicitation) after the assistant message ends.
|
|
@@ -1075,7 +274,7 @@ export async function handleAgentEvent(host, event) {
|
|
|
1075
274
|
case "tool_execution_start": {
|
|
1076
275
|
const { component, created } = registerPendingToolComponent(host, event.toolCallId, event.toolName, event.args, "standalone", () => new ToolExecutionComponent(event.toolName, event.args, { showImages: host.settingsManager.getShowImages() }, host.getRegisteredToolDefinition(event.toolName), host.ui));
|
|
1077
276
|
if (created) {
|
|
1078
|
-
renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
|
|
277
|
+
rs.renderedSegments.push({ kind: "tool", contentIndex: Number.MAX_SAFE_INTEGER, component });
|
|
1079
278
|
}
|
|
1080
279
|
host.ui.requestRender();
|
|
1081
280
|
break;
|
|
@@ -1121,9 +320,7 @@ export async function handleAgentEvent(host, event) {
|
|
|
1121
320
|
replaceCompactToolRowsWithPhaseSummary(host);
|
|
1122
321
|
host.streamingComponent = undefined;
|
|
1123
322
|
host.streamingMessage = undefined;
|
|
1124
|
-
|
|
1125
|
-
orphanedSegments = [];
|
|
1126
|
-
lastContentLength = 0;
|
|
323
|
+
rs.resetForSessionChange();
|
|
1127
324
|
host.pendingTools.clear();
|
|
1128
325
|
// Pinned output is only useful while work is actively streaming.
|
|
1129
326
|
// Keep chat history as the single source after completion.
|