From 2070fee3a47cf1e8d566ad3452ee64f0fc03dd5c Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Sun, 11 Jun 2023 12:26:42 -0400
Subject: Step to decide next step form user input

---
 continuedev/src/continuedev/core/policy.py         | 31 +++++++++++-------
 .../src/continuedev/server/state_manager.py        | 21 ++++++++++++
 continuedev/src/continuedev/steps/chat.py          | 13 ++++++++
 continuedev/src/continuedev/steps/react.py         | 37 ++++++++++++++++++++++
 4 files changed, 91 insertions(+), 11 deletions(-)
 create mode 100644 continuedev/src/continuedev/server/state_manager.py
 create mode 100644 continuedev/src/continuedev/steps/chat.py
 create mode 100644 continuedev/src/continuedev/steps/react.py

diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index 8aea8de7..0b0c0fcd 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -9,6 +9,8 @@ from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeSte
 from ..recipes.WritePytestsRecipe.main import WritePytestsRecipe
 from ..recipes.ContinueRecipeRecipe.main import ContinueStepStep
 from ..steps.comment_code import CommentCodeStep
+from ..steps.react import NLDecisionStep
+from ..steps.chat import SimpleChatStep
 
 
 class DemoPolicy(Policy):
@@ -26,19 +28,26 @@ class DemoPolicy(Policy):
         observation = history.get_current().observation
         if observation is not None and isinstance(observation, UserInputObservation):
             # This could be defined with ObservationTypePolicy. Ergonomics not right though.
-            if "/pytest" in observation.user_input.lower():
-                return WritePytestsRecipe(instructions=observation.user_input)
-            elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower():
+            user_input = observation.user_input
+            if "/pytest" in user_input.lower():
+                return WritePytestsRecipe(instructions=user_input)
+            elif "/dlt" in user_input.lower() or " dlt" in user_input.lower():
                 return CreatePipelineRecipe()
-            elif "/comment" in observation.user_input.lower():
+            elif "/comment" in user_input.lower():
                 return CommentCodeStep()
-            elif "/ask" in observation.user_input:
-                return AnswerQuestionChroma(question=" ".join(observation.user_input.split(" ")[1:]))
-            elif "/edit" in observation.user_input:
-                return EditFileChroma(request=" ".join(observation.user_input.split(" ")[1:]))
-            elif "/step" in observation.user_input:
-                return ContinueStepStep(prompt=" ".join(observation.user_input.split(" ")[1:]))
-            return EditHighlightedCodeStep(user_input=observation.user_input)
+            elif "/ask" in user_input:
+                return AnswerQuestionChroma(question=" ".join(user_input.split(" ")[1:]))
+            elif "/edit" in user_input:
+                return EditFileChroma(request=" ".join(user_input.split(" ")[1:]))
+            elif "/step" in user_input:
+                return ContinueStepStep(prompt=" ".join(user_input.split(" ")[1:]))
+            # return EditHighlightedCodeStep(user_input=user_input)
+            return NLDecisionStep(user_input=user_input, steps=[
+                EditHighlightedCodeStep(user_input=user_input),
+                AnswerQuestionChroma(question=user_input),
+                EditFileChroma(request=user_input),
+                SimpleChatStep(user_input=user_input)
+            ])
 
         state = history.get_current()
 
diff --git a/continuedev/src/continuedev/server/state_manager.py b/continuedev/src/continuedev/server/state_manager.py
new file mode 100644
index 00000000..c9bd760b
--- /dev/null
+++ b/continuedev/src/continuedev/server/state_manager.py
@@ -0,0 +1,21 @@
+from typing import Any, List, Tuple, Union
+from fastapi import WebSocket
+from pydantic import BaseModel
+from ..core.main import FullState
+
+# State updates represented as (path, replacement) pairs
+StateUpdate = Tuple[List[Union[str, int]], Any]
+
+
+class StateManager:
+    """
+    A class that acts as the source of truth for state, ingesting changes to the entire object and streaming only the updated portions to client.
+    """
+
+    def __init__(self, ws: WebSocket):
+        self.ws = ws
+
+    def _send_update(self, updates: List[StateUpdate]):
+        self.ws.send_json(
+            [update.dict() for update in updates]
+        )
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
new file mode 100644
index 00000000..817e10dd
--- /dev/null
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -0,0 +1,13 @@
+from textwrap import dedent
+from typing import List
+from ..core.main import Step
+from ..core.sdk import ContinueSDK
+from .main import MessageStep
+
+
+class SimpleChatStep(Step):
+    user_input: str
+
+    async def run(self, sdk: ContinueSDK):
+        # TODO: With history
+        self.description = await sdk.models.gpt35.complete(self.user_input)
diff --git a/continuedev/src/continuedev/steps/react.py b/continuedev/src/continuedev/steps/react.py
new file mode 100644
index 00000000..411adc87
--- /dev/null
+++ b/continuedev/src/continuedev/steps/react.py
@@ -0,0 +1,37 @@
+from textwrap import dedent
+from typing import List
+from ..core.main import Step
+from ..core.sdk import ContinueSDK
+from .main import MessageStep
+
+
+class NLDecisionStep(Step):
+    user_input: str
+    steps: List[Step]
+
+    async def run(self, sdk: ContinueSDK):
+        step_descriptions = "\n".join([
+            f"- {step.name}: {step.description}"
+            for step in self.steps
+        ])
+        prompt = dedent(f"""\
+                        The following steps are available, in the format "- [step name]: [step description]":
+                        {step_descriptions}
+                        
+                        The user gave the following input:
+                        
+                        {self.user_input}
+                        
+                        Select the step which should be taken next. Say only the name of the selected step:""")
+
+        resp = (await sdk.models.gpt35.complete(prompt)).lower()
+
+        step_to_run = None
+        for step in self.steps:
+            if step.name in resp:
+                step_to_run = step
+
+        step_to_run = step_to_run or MessageStep(
+            message="Unable to decide the next step")
+
+        await sdk.run_step(step_to_run)
-- 
cgit v1.2.3-70-g09d2


From af34f8eae701a875a2a85fca29ffc0d88deb1400 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 12 Jun 2023 10:48:03 -0700
Subject: better chat

---
 continuedev/src/continuedev/core/abstract_sdk.py          |  4 ++--
 continuedev/src/continuedev/core/policy.py                |  7 +++----
 continuedev/src/continuedev/core/sdk.py                   | 11 ++++++++---
 continuedev/src/continuedev/steps/chat.py                 |  5 ++---
 continuedev/src/continuedev/steps/core/core.py            |  2 +-
 continuedev/src/continuedev/steps/draft/migration.py      |  2 +-
 continuedev/src/continuedev/steps/find_and_replace.py     |  2 +-
 continuedev/src/continuedev/steps/input/nl_multiselect.py |  3 ++-
 continuedev/src/continuedev/steps/react.py                | 13 +++++++------
 docs/docs/concepts/sdk.md                                 |  2 +-
 10 files changed, 28 insertions(+), 23 deletions(-)

diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py
index 417971cd..3b85708d 100644
--- a/continuedev/src/continuedev/core/abstract_sdk.py
+++ b/continuedev/src/continuedev/core/abstract_sdk.py
@@ -88,6 +88,6 @@ class AbstractContinueSDK(ABC):
     def add_chat_context(self, content: str, role: ChatMessageRole = "assistent"):
         pass
 
-    @abstractproperty
-    def chat_context(self) -> List[ChatMessage]:
+    @abstractmethod
+    async def get_chat_context(self) -> List[ChatMessage]:
         pass
diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index 6a183598..e71a1cb2 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -37,7 +37,6 @@ class DemoPolicy(Policy):
                 return WritePytestsRecipe(instructions=user_input)
             elif "/dlt" in user_input.lower() or " dlt" in user_input.lower():
                 return CreatePipelineRecipe()
-            elif "/comment" in user_input.lower():
             if "/pytest" in observation.user_input.lower():
                 return WritePytestsRecipe(instructions=observation.user_input)
             elif "/dlt" in observation.user_input.lower():
@@ -59,10 +58,10 @@ class DemoPolicy(Policy):
             # return EditHighlightedCodeStep(user_input=user_input)
             return NLDecisionStep(user_input=user_input, steps=[
                 EditHighlightedCodeStep(user_input=user_input),
-                AnswerQuestionChroma(question=user_input),
-                EditFileChroma(request=user_input),
+                # AnswerQuestionChroma(question=user_input),
+                # EditFileChroma(request=user_input),
                 SimpleChatStep(user_input=user_input)
-            ])
+            ], default_step=EditHighlightedCodeStep(user_input=user_input))
 
         state = history.get_current()
 
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index 59bfc0f2..a94b5026 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -141,6 +141,11 @@ class ContinueSDK(AbstractContinueSDK):
         self.history.timeline[self.history.current_index].step.chat_context.append(
             ChatMessage(content=content, role=role))
 
-    @property
-    def chat_context(self) -> List[ChatMessage]:
-        return self.history.to_chat_history()
+    async def get_chat_context(self) -> List[ChatMessage]:
+        history_context = self.history.to_chat_history()
+        highlighted_code = await self.ide.getHighlightedCode()
+        for rif in highlighted_code:
+            code = await self.ide.readRangeInFile(rif)
+            history_context.append(ChatMessage(
+                content=f"The following code is highlighted:\n```\n{code}\n```", role="user"))
+        return history_context
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
index 817e10dd..80065c24 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -2,12 +2,11 @@ from textwrap import dedent
 from typing import List
 from ..core.main import Step
 from ..core.sdk import ContinueSDK
-from .main import MessageStep
+from .core.core import MessageStep
 
 
 class SimpleChatStep(Step):
     user_input: str
 
     async def run(self, sdk: ContinueSDK):
-        # TODO: With history
-        self.description = await sdk.models.gpt35.complete(self.user_input)
+        self.description = sdk.models.gpt35.complete(self.user_input, with_history=await sdk.get_chat_context())
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 53df65cc..57689f19 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -85,7 +85,7 @@ class ShellCommandsStep(Step):
                     {output}
                     ```
 
-                    This is a brief summary of the error followed by a suggestion on how it can be fixed:"""), with_history=sdk.chat_context)
+                    This is a brief summary of the error followed by a suggestion on how it can be fixed:"""), with_history=await sdk.get_chat_context())
 
                 sdk.raise_exception(
                     title="Error while running query", message=output, with_step=MessageStep(name=f"Suggestion to solve error {AI_ASSISTED_STRING}", message=suggestion)
diff --git a/continuedev/src/continuedev/steps/draft/migration.py b/continuedev/src/continuedev/steps/draft/migration.py
index f3b36b5e..7c4b7eb5 100644
--- a/continuedev/src/continuedev/steps/draft/migration.py
+++ b/continuedev/src/continuedev/steps/draft/migration.py
@@ -13,7 +13,7 @@ class MigrationStep(Step):
         recent_edits = await sdk.ide.get_recent_edits(self.edited_file)
         recent_edits_string = "\n\n".join(
             map(lambda x: x.to_string(), recent_edits))
-        description = await sdk.models.gpt35.complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n")
+        description = sdk.models.gpt35.complete(f"{recent_edits_string}\n\nGenerate a short description of the migration made in the above changes:\n")
         await sdk.run([
             "cd libs",
             "poetry run alembic revision --autogenerate -m " + description,
diff --git a/continuedev/src/continuedev/steps/find_and_replace.py b/continuedev/src/continuedev/steps/find_and_replace.py
index fec33997..690872c0 100644
--- a/continuedev/src/continuedev/steps/find_and_replace.py
+++ b/continuedev/src/continuedev/steps/find_and_replace.py
@@ -10,7 +10,7 @@ class FindAndReplaceStep(Step):
     replacement: str
 
     async def describe(self, models: Models):
-        return f"Replace all instances of `{self.pattern}` with `{self.replacement}` in `{self.filepath}`"
+        return f"Replaced all instances of `{self.pattern}` with `{self.replacement}` in `{self.filepath}`"
 
     async def run(self, sdk: ContinueSDK):
         file_content = await sdk.ide.readFile(self.filepath)
diff --git a/continuedev/src/continuedev/steps/input/nl_multiselect.py b/continuedev/src/continuedev/steps/input/nl_multiselect.py
index c3c832f5..36c489c7 100644
--- a/continuedev/src/continuedev/steps/input/nl_multiselect.py
+++ b/continuedev/src/continuedev/steps/input/nl_multiselect.py
@@ -23,5 +23,6 @@ class NLMultiselectStep(Step):
         if first_try is not None:
             return first_try
 
-        gpt_parsed = await sdk.models.gpt35.complete(f"These are the available options are: [{', '.join(self.options)}]. The user requested {user_response}. This is the exact string from the options array that they selected:")
+        gpt_parsed = sdk.models.gpt35.complete(
+            f"These are the available options are: [{', '.join(self.options)}]. The user requested {user_response}. This is the exact string from the options array that they selected:")
         return extract_option(gpt_parsed) or self.options[0]
diff --git a/continuedev/src/continuedev/steps/react.py b/continuedev/src/continuedev/steps/react.py
index 411adc87..6b6024ce 100644
--- a/continuedev/src/continuedev/steps/react.py
+++ b/continuedev/src/continuedev/steps/react.py
@@ -1,13 +1,15 @@
 from textwrap import dedent
-from typing import List
+from typing import List, Union
 from ..core.main import Step
 from ..core.sdk import ContinueSDK
-from .main import MessageStep
+from .core.core import MessageStep
 
 
 class NLDecisionStep(Step):
     user_input: str
     steps: List[Step]
+    hide: bool = True
+    default_step: Union[Step, None] = None
 
     async def run(self, sdk: ContinueSDK):
         step_descriptions = "\n".join([
@@ -24,14 +26,13 @@ class NLDecisionStep(Step):
                         
                         Select the step which should be taken next. Say only the name of the selected step:""")
 
-        resp = (await sdk.models.gpt35.complete(prompt)).lower()
+        resp = sdk.models.gpt35.complete(prompt).lower()
 
         step_to_run = None
         for step in self.steps:
-            if step.name in resp:
+            if step.name.lower() in resp:
                 step_to_run = step
 
-        step_to_run = step_to_run or MessageStep(
-            message="Unable to decide the next step")
+        step_to_run = step_to_run or self.default_step or self.steps[0]
 
         await sdk.run_step(step_to_run)
diff --git a/docs/docs/concepts/sdk.md b/docs/docs/concepts/sdk.md
index 30da2e79..21190aa8 100644
--- a/docs/docs/concepts/sdk.md
+++ b/docs/docs/concepts/sdk.md
@@ -23,7 +23,7 @@ The **Continue SDK** gives you all the tools you need to automate software devel
 
 ### `sdk.models`
 
-`sdk.models` is an instance of the `Models` class, containing many of the most commonly used LLMs or other foundation models. You can instantiate a model (starcoder for example) (this is too awkward rn, I know) by calling `starcoder = await sdk.models.starcoder()`. Right now, all of the models are `LLM`s, meaning that they offer the `complete` method, used like `bubble_sort_code = await starcoder.complete("# Write a bubble sort function below, in Python:\n")`.
+`sdk.models` is an instance of the `Models` class, containing many of the most commonly used LLMs or other foundation models. You can access a model (starcoder for example) like `starcoder = sdk.models.starcoder`. Right now, all of the models are `LLM`s, meaning that they offer the `complete` method, used like `bubble_sort_code = await starcoder.complete("# Write a bubble sort function below, in Python:\n")`.
 
 ### `sdk.history`
 
-- 
cgit v1.2.3-70-g09d2


From 40ba9eaf82a1386ccacf5046c072df3d131d5284 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 12 Jun 2023 10:50:33 -0700
Subject: calculate diff and highlight changes

---
 continuedev/poetry.lock                            |  34 +++--
 continuedev/pyproject.toml                         |   1 +
 continuedev/src/continuedev/core/sdk.py            |   3 +
 .../src/continuedev/libs/util/calculate_diff.py    | 152 +++++++++++++++++++++
 .../src/continuedev/libs/util/copy_codebase.py     |  38 +-----
 .../src/continuedev/models/filesystem_edit.py      |   4 +-
 continuedev/src/continuedev/steps/chat.py          |  12 ++
 continuedev/src/continuedev/steps/react.py         |  33 +++--
 extension/package.json                             |  80 -----------
 extension/react-app/src/components/CodeBlock.tsx   |   1 +
 .../react-app/src/components/StepContainer.tsx     |  23 +++-
 extension/react-app/src/tabs/chat/MessageDiv.tsx   |   4 +-
 extension/react-app/src/tabs/gui.tsx               |  20 ++-
 extension/src/continueIdeClient.ts                 |  31 ++++-
 extension/src/decorations.ts                       |  19 ++-
 15 files changed, 298 insertions(+), 157 deletions(-)
 create mode 100644 continuedev/src/continuedev/libs/util/calculate_diff.py

diff --git a/continuedev/poetry.lock b/continuedev/poetry.lock
index 857a7c99..4aedce87 100644
--- a/continuedev/poetry.lock
+++ b/continuedev/poetry.lock
@@ -344,6 +344,21 @@ typing-inspect = ">=0.4.0"
 [package.extras]
 dev = ["flake8", "hypothesis", "ipython", "mypy (>=0.710)", "portray", "pytest (>=6.2.3)", "simplejson", "types-dataclasses"]
 
+[[package]]
+name = "diff-match-patch"
+version = "20230430"
+description = "Diff Match and Patch"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "diff-match-patch-20230430.tar.gz", hash = "sha256:953019cdb9c9d2c9e47b5b12bcff3cf4746fc4598eb406076fa1fc27e6a1f15c"},
+    {file = "diff_match_patch-20230430-py3-none-any.whl", hash = "sha256:dce43505fb7b1b317de7195579388df0746d90db07015ed47a85e5e44930ef93"},
+]
+
+[package.extras]
+dev = ["attribution (==1.6.2)", "black (==23.3.0)", "flit (==3.8.0)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"]
+
 [[package]]
 name = "fastapi"
 version = "0.95.1"
@@ -1251,23 +1266,6 @@ urllib3 = ">=1.21.1,<1.27"
 socks = ["PySocks (>=1.5.6,!=1.5.7)"]
 use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
 
-[[package]]
-name = "setuptools"
-version = "67.7.2"
-description = "Easily download, build, install, upgrade, and uninstall Python packages"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-files = [
-    {file = "setuptools-67.7.2-py3-none-any.whl", hash = "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b"},
-    {file = "setuptools-67.7.2.tar.gz", hash = "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"},
-]
-
-[package.extras]
-docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
-testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
-
 [[package]]
 name = "six"
 version = "1.16.0"
@@ -1739,4 +1737,4 @@ multidict = ">=4.0"
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.9"
-content-hash = "9f9254c954b7948c49debba86bc81a4a9c3f50694424f5940d0058725b1bf0fb"
+content-hash = "0f5f759bac0e44a1fbcc9babeccdea8688ea2226a4bae7a13858542ae03a3228"
diff --git a/continuedev/pyproject.toml b/continuedev/pyproject.toml
index 631742ec..7315e79d 100644
--- a/continuedev/pyproject.toml
+++ b/continuedev/pyproject.toml
@@ -7,6 +7,7 @@ readme = "README.md"
 
 [tool.poetry.dependencies]
 python = "^3.9"
+diff-match-patch = "^20230430"
 fastapi = "^0.95.1"
 typer = "^0.7.0"
 openai = "^0.27.5"
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index a94b5026..2849b0c8 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -149,3 +149,6 @@ class ContinueSDK(AbstractContinueSDK):
             history_context.append(ChatMessage(
                 content=f"The following code is highlighted:\n```\n{code}\n```", role="user"))
         return history_context
+
+    async def update_ui(self):
+        await self.__autopilot.update_subscribers()
diff --git a/continuedev/src/continuedev/libs/util/calculate_diff.py b/continuedev/src/continuedev/libs/util/calculate_diff.py
new file mode 100644
index 00000000..d778891b
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/calculate_diff.py
@@ -0,0 +1,152 @@
+import difflib
+from typing import List
+from ...models.main import Position, Range
+from ...models.filesystem import FileEdit
+from diff_match_patch import diff_match_patch
+
+
+def calculate_diff_match_patch(filepath: str, original: str, updated: str) -> List[FileEdit]:
+    dmp = diff_match_patch()
+    diffs = dmp.diff_main(original, updated)
+    dmp.diff_cleanupSemantic(diffs)
+
+    replacements = []
+
+    current_index = 0
+    deleted_length = 0
+
+    for diff in diffs:
+        if diff[0] == diff_match_patch.DIFF_EQUAL:
+            current_index += len(diff[1])
+            deleted_length = 0
+        elif diff[0] == diff_match_patch.DIFF_INSERT:
+            current_index += deleted_length
+            replacements.append((current_index, current_index, diff[1]))
+            current_index += len(diff[1])
+            deleted_length = 0
+        elif diff[0] == diff_match_patch.DIFF_DELETE:
+            replacements.append(
+                (current_index, current_index + len(diff[1]), ''))
+            deleted_length += len(diff[1])
+        elif diff[0] == diff_match_patch.DIFF_REPLACE:
+            replacements.append(
+                (current_index, current_index + len(diff[1]), ''))
+            current_index += deleted_length
+            replacements.append((current_index, current_index, diff[2]))
+            current_index += len(diff[2])
+            deleted_length = 0
+
+    return [FileEdit(filepath=filepath, range=Range.from_indices(original, r[0], r[1]), replacement=r[2]) for r in replacements]
+
+
+def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit]:
+    s = difflib.SequenceMatcher(None, original, updated)
+    offset = 0  # The indices are offset by previous deletions/insertions
+    edits = []
+    for tag, i1, i2, j1, j2 in s.get_opcodes():
+        i1, i2, j1, j2 = i1 + offset, i2 + offset, j1 + offset, j2 + offset
+        replacement = updated[j1:j2]
+        if tag == "equal":
+            pass
+        elif tag == "delete":
+            edits.append(FileEdit.from_deletion(
+                filepath, Range.from_indices(original, i1, i2)))
+            offset -= i2 - i1
+        elif tag == "insert":
+            edits.append(FileEdit.from_insertion(
+                filepath, Position.from_index(original, i1), replacement))
+            offset += j2 - j1
+        elif tag == "replace":
+            edits.append(FileEdit(filepath=filepath, range=Range.from_indices(
+                original, i1, i2), replacement=replacement))
+            offset += (j2 - j1) - (i2 - i1)
+        else:
+            raise Exception("Unexpected difflib.SequenceMatcher tag: " + tag)
+
+    return edits
+
+
+def calculate_diff2(filepath: str, original: str, updated: str) -> List[FileEdit]:
+    edits = []
+    max_iterations = 1000
+    i = 0
+    while not original == updated:
+        # TODO - For some reason it can't handle a single newline at the end of the file?
+        s = difflib.SequenceMatcher(None, original, updated)
+        opcodes = s.get_opcodes()
+        for edit_index in range(len(opcodes)):
+            tag, i1, i2, j1, j2 = s.get_opcodes()[edit_index]
+            replacement = updated[j1:j2]
+            if tag == "equal":
+                continue
+            elif tag == "delete":
+                edits.append(FileEdit.from_deletion(
+                    filepath, Range.from_indices(original, i1, i2)))
+            elif tag == "insert":
+                edits.append(FileEdit.from_insertion(
+                    filepath, Position.from_index(original, i1), replacement))
+            elif tag == "replace":
+                edits.append(FileEdit(filepath=filepath, range=Range.from_indices(
+                    original, i1, i2), replacement=replacement))
+            else:
+                raise Exception(
+                    "Unexpected difflib.SequenceMatcher tag: " + tag)
+            break
+
+        original = apply_edit_to_str(original, edits[-1])
+
+        i += 1
+        if i > max_iterations:
+            raise Exception("Max iterations reached")
+
+    return edits
+
+
+def read_range_in_str(s: str, r: Range) -> str:
+    lines = s.splitlines()[r.start.line:r.end.line + 1]
+    if len(lines) == 0:
+        return ""
+
+    lines[0] = lines[0][r.start.character:]
+    lines[-1] = lines[-1][:r.end.character + 1]
+    return "\n".join(lines)
+
+
+def apply_edit_to_str(s: str, edit: FileEdit) -> str:
+    original = read_range_in_str(s, edit.range)
+
+    # Split lines and deal with some edge cases (could obviously be nicer)
+    lines = s.splitlines()
+    if s.startswith("\n"):
+        lines.insert(0, "")
+    if s.endswith("\n"):
+        lines.append("")
+
+    if len(lines) == 0:
+        lines = [""]
+
+    end = Position(line=edit.range.end.line,
+                   character=edit.range.end.character)
+    if edit.range.end.line == len(lines) and edit.range.end.character == 0:
+        end = Position(line=edit.range.end.line - 1,
+                       character=len(lines[min(len(lines) - 1, edit.range.end.line - 1)]))
+
+    before_lines = lines[:edit.range.start.line]
+    after_lines = lines[end.line + 1:]
+    between_str = lines[min(len(lines) - 1, edit.range.start.line)][:edit.range.start.character] + \
+        edit.replacement + \
+        lines[min(len(lines) - 1, end.line)][end.character + 1:]
+
+    new_range = Range(
+        start=edit.range.start,
+        end=Position(
+            line=edit.range.start.line +
+            len(edit.replacement.splitlines()) - 1,
+            character=edit.range.start.character +
+            len(edit.replacement.splitlines()
+                [-1]) if edit.replacement != "" else 0
+        )
+    )
+
+    lines = before_lines + between_str.splitlines() + after_lines
+    return "\n".join(lines)
diff --git a/continuedev/src/continuedev/libs/util/copy_codebase.py b/continuedev/src/continuedev/libs/util/copy_codebase.py
index af957a34..97143faf 100644
--- a/continuedev/src/continuedev/libs/util/copy_codebase.py
+++ b/continuedev/src/continuedev/libs/util/copy_codebase.py
@@ -3,13 +3,12 @@ from pathlib import Path
 from typing import Iterable, List, Union
 from watchdog.observers import Observer
 from watchdog.events import PatternMatchingEventHandler
-from ..models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, Position, Range, RenameFile, RenameDirectory, SequentialFileSystemEdit
-from ..models.filesystem import FileSystem
-from ..libs.main import Autopilot
-from ..libs.map_path import map_path
-from ..libs.steps.main import ManualEditAction
+from ...models.main import FileEdit, DeleteDirectory, DeleteFile, AddDirectory, AddFile, FileSystemEdit, RenameFile, RenameDirectory, SequentialFileSystemEdit
+from ...models.filesystem import FileSystem
+from ...core.autopilot import Autopilot
+from .map_path import map_path
+from ...core.sdk import ManualEditStep
 import shutil
-import difflib
 
 
 def create_copy(orig_root: str, copy_root: str = None, ignore: Iterable[str] = []):
@@ -36,33 +35,6 @@ def create_copy(orig_root: str, copy_root: str = None, ignore: Iterable[str] = [
                 os.symlink(child, map_path(child))
 
 
-def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit]:
-    s = difflib.SequenceMatcher(None, original, updated)
-    offset = 0  # The indices are offset by previous deletions/insertions
-    edits = []
-    for tag, i1, i2, j1, j2 in s.get_opcodes():
-        i1, i2, j1, j2 = i1 + offset, i2 + offset, j1 + offset, j2 + offset
-        replacement = updated[j1:j2]
-        if tag == "equal":
-            pass
-        elif tag == "delete":
-            edits.append(FileEdit.from_deletion(
-                filepath, Range.from_indices(original, i1, i2)))
-            offset -= i2 - i1
-        elif tag == "insert":
-            edits.append(FileEdit.from_insertion(
-                filepath, Position.from_index(original, i1), replacement))
-            offset += j2 - j1
-        elif tag == "replace":
-            edits.append(FileEdit(filepath, Range.from_indices(
-                original, i1, i2), replacement))
-            offset += (j2 - j1) - (i2 + i1)
-        else:
-            raise Exception("Unexpected difflib.SequenceMatcher tag: " + tag)
-
-    return edits
-
-
 # The whole usage of watchdog here should only be specific to RealFileSystem, you want to have a different "Observer" class for VirtualFileSystem, which would depend on being sent notifications
 class CopyCodebaseEventHandler(PatternMatchingEventHandler):
     def __init__(self, ignore_directories: List[str], ignore_patterns: List[str], autopilot: Autopilot, orig_root: str, copy_root: str, filesystem: FileSystem):
diff --git a/continuedev/src/continuedev/models/filesystem_edit.py b/continuedev/src/continuedev/models/filesystem_edit.py
index 8e74b819..b06ca2b3 100644
--- a/continuedev/src/continuedev/models/filesystem_edit.py
+++ b/continuedev/src/continuedev/models/filesystem_edit.py
@@ -30,8 +30,8 @@ class FileEdit(AtomicFileSystemEdit):
         return FileEdit(map_path(self.filepath, orig_root, copy_root), self.range, self.replacement)
 
     @staticmethod
-    def from_deletion(filepath: str, start: Position, end: Position) -> "FileEdit":
-        return FileEdit(filepath, Range(start, end), "")
+    def from_deletion(filepath: str, range: Range) -> "FileEdit":
+        return FileEdit(filepath=filepath, range=range, replacement="")
 
     @staticmethod
     def from_insertion(filepath: str, position: Position, content: str) -> "FileEdit":
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
index 80065c24..56e49223 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -7,6 +7,18 @@ from .core.core import MessageStep
 
 class SimpleChatStep(Step):
     user_input: str
+    name: str = "Chat"
 
     async def run(self, sdk: ContinueSDK):
+<<<<<<< Updated upstream
         self.description = sdk.models.gpt35.complete(self.user_input, with_history=await sdk.get_chat_context())
+=======
+        # TODO: With history
+        self.description = ""
+        for chunk in sdk.models.gpt35.stream_chat([{"role": "user", "content": self.user_input}]):
+            self.description += chunk
+            await sdk.update_ui()
+
+        self.name = sdk.models.gpt35.complete(
+            f"Write a short title for the following chat message: {self.description}").strip()
+>>>>>>> Stashed changes
diff --git a/continuedev/src/continuedev/steps/react.py b/continuedev/src/continuedev/steps/react.py
index 6b6024ce..d98b41c6 100644
--- a/continuedev/src/continuedev/steps/react.py
+++ b/continuedev/src/continuedev/steps/react.py
@@ -1,5 +1,9 @@
 from textwrap import dedent
+<<<<<<< Updated upstream
 from typing import List, Union
+=======
+from typing import List, Tuple
+>>>>>>> Stashed changes
 from ..core.main import Step
 from ..core.sdk import ContinueSDK
 from .core.core import MessageStep
@@ -7,31 +11,42 @@ from .core.core import MessageStep
 
 class NLDecisionStep(Step):
     user_input: str
+<<<<<<< Updated upstream
     steps: List[Step]
     hide: bool = True
     default_step: Union[Step, None] = None
+=======
+    steps: List[Tuple[Step, str]]
+
+    hide: bool = True
+>>>>>>> Stashed changes
 
     async def run(self, sdk: ContinueSDK):
         step_descriptions = "\n".join([
-            f"- {step.name}: {step.description}"
+            f"- {step[0].name}: {step[1]}"
             for step in self.steps
         ])
         prompt = dedent(f"""\
-                        The following steps are available, in the format "- [step name]: [step description]":
-                        {step_descriptions}
-                        
-                        The user gave the following input:
-                        
-                        {self.user_input}
-                        
-                        Select the step which should be taken next. Say only the name of the selected step:""")
+            The following steps are available, in the format "- [step name]: [step description]":
+            {step_descriptions}
+            
+            The user gave the following input:
+            
+            {self.user_input}
+            
+            Select the step which should be taken next to satisfy the user input. Say only the name of the selected step. You must choose one:""")
 
         resp = sdk.models.gpt35.complete(prompt).lower()
 
         step_to_run = None
         for step in self.steps:
+<<<<<<< Updated upstream
             if step.name.lower() in resp:
                 step_to_run = step
+=======
+            if step[0].name.lower() in resp:
+                step_to_run = step[0]
+>>>>>>> Stashed changes
 
         step_to_run = step_to_run or self.default_step or self.steps[0]
 
diff --git a/extension/package.json b/extension/package.json
index 1d2fd995..8ee8cb4c 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -54,93 +54,13 @@
       }
     },
     "commands": [
-      {
-        "command": "continue.writeDocstring",
-        "category": "Continue",
-        "title": "Write a docstring for the current function"
-      },
       {
         "command": "continue.openContinueGUI",
         "category": "Continue",
         "title": "Open Continue GUI"
-      },
-      {
-        "command": "continue.askQuestionFromInput",
-        "Category": "Continue",
-        "title": "Ask a question from input box"
-      },
-      {
-        "command": "continue.openCapturedTerminal",
-        "Category": "Continue",
-        "title": "Open Captured Terminal"
-      },
-      {
-        "command": "continue.askQuestion",
-        "Category": "Continue",
-        "title": "Ask a question from webview"
-      },
-      {
-        "command": "continue.createTerminal",
-        "category": "Continue",
-        "title": "Create Terminal"
-      },
-      {
-        "command": "continue.debugTest",
-        "category": "Continue",
-        "title": "Debug Test"
-      },
-      {
-        "command": "continue.suggestionDown",
-        "category": "Continue",
-        "title": "Suggestion Down"
-      },
-      {
-        "command": "continue.suggestionUp",
-        "category": "Continue",
-        "title": "Suggestion Up"
-      },
-      {
-        "command": "continue.acceptSuggestion",
-        "category": "Continue",
-        "title": "Accept Suggestion"
-      },
-      {
-        "command": "continue.rejectSuggestion",
-        "category": "Continue",
-        "title": "Reject Suggestion"
-      },
-      {
-        "command": "continue.writeUnitTest",
-        "title": "Write Unit Test",
-        "category": "Continue"
-      },
-      {
-        "command": "continue.findSuspiciousCode",
-        "title": "Find Suspicious Code",
-        "category": "Continue"
-      },
-      {
-        "command": "continue.focusContinueInput",
-        "title": "Focus Continue Input",
-        "category": "Continue"
       }
     ],
     "keybindings": [
-      {
-        "command": "continue.suggestionDown",
-        "mac": "shift+ctrl+down",
-        "key": "shift+ctrl+down"
-      },
-      {
-        "command": "continue.suggestionUp",
-        "mac": "shift+ctrl+up",
-        "key": "shift+ctrl+up"
-      },
-      {
-        "command": "continue.acceptSuggestion",
-        "mac": "shift+ctrl+enter",
-        "key": "shift+ctrl+enter"
-      },
       {
         "command": "continue.focusContinueInput",
         "mac": "cmd+k",
diff --git a/extension/react-app/src/components/CodeBlock.tsx b/extension/react-app/src/components/CodeBlock.tsx
index e0336554..eedae3fb 100644
--- a/extension/react-app/src/components/CodeBlock.tsx
+++ b/extension/react-app/src/components/CodeBlock.tsx
@@ -11,6 +11,7 @@ const StyledPre = styled.pre`
   border: 1px solid gray;
   border-radius: ${defaultBorderRadius};
   background-color: ${vscBackground};
+  padding: 8px;
 `;
 
 const StyledCode = styled.code`
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index 8ea54325..fb0143b5 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -84,6 +84,15 @@ const OnHoverDiv = styled.div`
   animation: ${appear} 0.3s ease-in-out;
 `;
 
+const MarkdownPre = styled.pre`
+  background-color: ${secondaryDark};
+  padding: 10px;
+  border-radius: ${defaultBorderRadius};
+  border: 0.5px solid white;
+`;
+
+const MarkdownCode = styled.code``;
+
 function StepContainer(props: StepContainerProps) {
   const [open, setOpen] = useState(
     typeof props.open === "undefined" ? true : props.open
@@ -182,7 +191,19 @@ function StepContainer(props: StepContainerProps) {
               {props.historyNode.observation.error as string}
             </pre>
           ) : (
-            <ReactMarkdown key={1} className="overflow-scroll">
+            <ReactMarkdown
+              key={1}
+              className="overflow-scroll"
+              components={{
+                pre: ({ node, ...props }) => {
+                  return (
+                    <CodeBlock
+                      children={props.children[0] as string}
+                    ></CodeBlock>
+                  );
+                },
+              }}
+            >
               {props.historyNode.step.description as any}
             </ReactMarkdown>
           )}
diff --git a/extension/react-app/src/tabs/chat/MessageDiv.tsx b/extension/react-app/src/tabs/chat/MessageDiv.tsx
index 1d7bb5f5..3543dd93 100644
--- a/extension/react-app/src/tabs/chat/MessageDiv.tsx
+++ b/extension/react-app/src/tabs/chat/MessageDiv.tsx
@@ -58,7 +58,9 @@ function MessageDiv(props: ChatMessage) {
   }, [richContent, isStreaming]);
 
   useEffect(() => {
-    setRichContent([<ReactMarkdown key={1}>{props.content}</ReactMarkdown>]);
+    setRichContent([
+      <ReactMarkdown key={1} children={props.content}></ReactMarkdown>,
+    ]);
   }, [props.content]);
 
   return (
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index 5c75579b..9f7e651f 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -41,7 +41,7 @@ function GUI(props: GUIProps) {
   //         name: "Waiting for user input",
   //         cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
   //         description:
-  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`",
+  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
   //       },
   //       observation: {
   //         title: "ERROR FOUND",
@@ -92,7 +92,7 @@ function GUI(props: GUIProps) {
   //         prompt:
   //           "I ran into this problem with my Python code:\n\n                Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n                Below are the files that might need to be fixed:\n\n                {code}\n\n                This is what the code should be in order to avoid the problem:\n",
   //         description:
-  //           "Editing files: /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
   //       },
   //       output: [
   //         null,
@@ -154,22 +154,30 @@ function GUI(props: GUIProps) {
   //       output: [null, null],
   //     },
   //   ],
-  //   current_index: 0,
+  //   current_index: 3,
   // } as any);
 
   const topGuiDivRef = useRef<HTMLDivElement>(null);
   const client = useContinueGUIProtocol();
 
+  const [scrollTimeout, setScrollTimeout] = useState<NodeJS.Timeout | null>(
+    null
+  );
   const scrollToBottom = useCallback(() => {
+    if (scrollTimeout) {
+      clearTimeout(scrollTimeout);
+    }
+    // Debounced smooth scroll to bottom of screen
     if (topGuiDivRef.current) {
-      setTimeout(() => {
+      const timeout = setTimeout(() => {
         window.scrollTo({
           top: window.outerHeight,
           behavior: "smooth",
         });
-      }, 100);
+      }, 200);
+      setScrollTimeout(timeout);
     }
-  }, [topGuiDivRef.current]);
+  }, [topGuiDivRef.current, scrollTimeout]);
 
   useEffect(() => {
     console.log("CLIENT ON STATE UPDATE: ", client, client?.onStateUpdate);
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index bbaf5f08..c395ae0e 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -13,6 +13,7 @@ import { FileEditWithFullContents } from "../schema/FileEditWithFullContents";
 import fs = require("fs");
 import { WebsocketMessenger } from "./util/messenger";
 import { CapturedTerminal } from "./terminal/terminalEmulator";
+import { decorationManager } from "./decorations";
 
 class IdeProtocolClient {
   private messenger: WebsocketMessenger | null = null;
@@ -281,8 +282,36 @@ class IdeProtocolClient {
           edit.range.start.line,
           edit.range.start.character,
           edit.range.end.line,
-          edit.range.end.character + 1
+          edit.range.end.character
         );
+        const decorationKey =
+          edit.replacement === ""
+            ? {
+                editorUri: editor.document.uri.fsPath,
+                options: {
+                  range: new vscode.Range(
+                    new vscode.Position(range.start.line, 0),
+                    new vscode.Position(range.end.line + 1, 0)
+                  ),
+                  // after: {
+                  //   contentText: "Removed",
+                  // },
+                },
+                decorationType: vscode.window.createTextEditorDecorationType({
+                  backgroundColor: "rgba(255, 0, 0, 0.2)",
+                }),
+              }
+            : {
+                editorUri: editor.document.uri.fsPath,
+                options: {
+                  range,
+                },
+                decorationType: vscode.window.createTextEditorDecorationType({
+                  backgroundColor: "rgba(66, 105, 55, 1.0)",
+                  isWholeLine: true,
+                }),
+              };
+        decorationManager.addDecoration(decorationKey);
         editor.edit((editBuilder) => {
           this._makingEdit += 2; // editBuilder.replace takes 2 edits: delete and insert
           editBuilder.replace(range, edit.replacement);
diff --git a/extension/src/decorations.ts b/extension/src/decorations.ts
index 456f0c10..d2c94135 100644
--- a/extension/src/decorations.ts
+++ b/extension/src/decorations.ts
@@ -94,15 +94,22 @@ class DecorationManager {
       decorationTypes = new Map();
       decorationTypes.set(key.decorationType, [key.options]);
       this.editorToDecorations.set(key.editorUri, decorationTypes);
-    }
-
-    const decorations = decorationTypes.get(key.decorationType);
-    if (!decorations) {
-      decorationTypes.set(key.decorationType, [key.options]);
     } else {
-      decorations.push(key.options);
+      const decorations = decorationTypes.get(key.decorationType);
+      if (!decorations) {
+        decorationTypes.set(key.decorationType, [key.options]);
+      } else {
+        decorations.push(key.options);
+      }
     }
+
     this.rerenderDecorations(key.editorUri, key.decorationType);
+
+    vscode.window.onDidChangeTextEditorSelection((event) => {
+      if (event.textEditor.document.fileName === key.editorUri) {
+        this.deleteAllDecorations(key.editorUri);
+      }
+    });
   }
 
   deleteDecoration(key: DecorationKey) {
-- 
cgit v1.2.3-70-g09d2


From 57a6d8fc0a808ca160b5f691882a7893ed438c97 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 12 Jun 2023 11:00:34 -0700
Subject: streaming and highlighted code to chat context

---
 continuedev/src/continuedev/core/policy.py     |  6 ++--
 continuedev/src/continuedev/libs/llm/openai.py | 38 +++++++++++++-------------
 continuedev/src/continuedev/steps/chat.py      |  7 +----
 continuedev/src/continuedev/steps/react.py     | 16 +----------
 4 files changed, 25 insertions(+), 42 deletions(-)

diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index e71a1cb2..00b5427c 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -57,10 +57,12 @@ class DemoPolicy(Policy):
                 return ContinueStepStep(prompt=" ".join(user_input.split(" ")[1:]))
             # return EditHighlightedCodeStep(user_input=user_input)
             return NLDecisionStep(user_input=user_input, steps=[
-                EditHighlightedCodeStep(user_input=user_input),
+                (EditHighlightedCodeStep(user_input=user_input),
+                 "Edit the highlighted code"),
                 # AnswerQuestionChroma(question=user_input),
                 # EditFileChroma(request=user_input),
-                SimpleChatStep(user_input=user_input)
+                (SimpleChatStep(user_input=user_input),
+                 "Respond to the user with a chat message"),
             ], default_step=EditHighlightedCodeStep(user_input=user_input))
 
         state = history.get_current()
diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py
index 6a537afd..9b8d3447 100644
--- a/continuedev/src/continuedev/libs/llm/openai.py
+++ b/continuedev/src/continuedev/libs/llm/openai.py
@@ -23,7 +23,7 @@ class OpenAI(LLM):
     def with_system_message(self, system_message: Union[str, None]):
         return OpenAI(api_key=self.api_key, system_message=system_message)
 
-    def stream_chat(self, messages, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+    def stream_chat(self, prompt, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
         self.completion_count += 1
         args = {"max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5, "top_p": 1,
                 "frequency_penalty": 0, "presence_penalty": 0} | kwargs
@@ -31,7 +31,7 @@ class OpenAI(LLM):
         args["model"] = "gpt-3.5-turbo"
 
         for chunk in openai.ChatCompletion.create(
-            messages=messages,
+            messages=self.compile_chat_messages(with_history, prompt),
             **args,
         ):
             if "content" in chunk.choices[0].delta:
@@ -39,7 +39,21 @@ class OpenAI(LLM):
             else:
                 continue
 
-    def stream_complete(self, prompt: str, **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
+    def compile_chat_messages(self, msgs: List[ChatMessage], prompt: str) -> List[Dict]:
+        history = []
+        if self.system_message:
+            history.append({
+                "role": "system",
+                "content": self.system_message
+            })
+        history += [msg.dict() for msg in msgs]
+        history.append({
+            "role": "user",
+            "content": prompt
+        })
+        return history
+
+    def stream_complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> Generator[Union[Any, List, Dict], None, None]:
         self.completion_count += 1
         args = {"model": self.default_model, "max_tokens": DEFAULT_MAX_TOKENS, "temperature": 0.5,
                 "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0, "suffix": None} | kwargs
@@ -47,10 +61,7 @@ class OpenAI(LLM):
 
         if args["model"] == "gpt-3.5-turbo":
             generator = openai.ChatCompletion.create(
-                messages=[{
-                    "role": "user",
-                    "content": prompt
-                }],
+                messages=self.compile_chat_messages(with_history, prompt),
                 **args,
             )
             for chunk in generator:
@@ -71,19 +82,8 @@ class OpenAI(LLM):
                 "frequency_penalty": 0, "presence_penalty": 0, "stream": False} | kwargs
 
         if args["model"] == "gpt-3.5-turbo":
-            messages = []
-            if self.system_message:
-                messages.append({
-                    "role": "system",
-                    "content": self.system_message
-                })
-            messages += [msg.dict() for msg in with_history]
-            messages.append({
-                "role": "user",
-                "content": prompt
-            })
             resp = openai.ChatCompletion.create(
-                messages=messages,
+                messages=self.compile_chat_messages(with_history, prompt),
                 **args,
             ).choices[0].message.content
         else:
diff --git a/continuedev/src/continuedev/steps/chat.py b/continuedev/src/continuedev/steps/chat.py
index 56e49223..aadcfa8e 100644
--- a/continuedev/src/continuedev/steps/chat.py
+++ b/continuedev/src/continuedev/steps/chat.py
@@ -10,15 +10,10 @@ class SimpleChatStep(Step):
     name: str = "Chat"
 
     async def run(self, sdk: ContinueSDK):
-<<<<<<< Updated upstream
-        self.description = sdk.models.gpt35.complete(self.user_input, with_history=await sdk.get_chat_context())
-=======
-        # TODO: With history
         self.description = ""
-        for chunk in sdk.models.gpt35.stream_chat([{"role": "user", "content": self.user_input}]):
+        for chunk in sdk.models.gpt35.stream_chat(self.user_input, with_history=await sdk.get_chat_context()):
             self.description += chunk
             await sdk.update_ui()
 
         self.name = sdk.models.gpt35.complete(
             f"Write a short title for the following chat message: {self.description}").strip()
->>>>>>> Stashed changes
diff --git a/continuedev/src/continuedev/steps/react.py b/continuedev/src/continuedev/steps/react.py
index d98b41c6..d825d424 100644
--- a/continuedev/src/continuedev/steps/react.py
+++ b/continuedev/src/continuedev/steps/react.py
@@ -1,9 +1,5 @@
 from textwrap import dedent
-<<<<<<< Updated upstream
-from typing import List, Union
-=======
-from typing import List, Tuple
->>>>>>> Stashed changes
+from typing import List, Union, Tuple
 from ..core.main import Step
 from ..core.sdk import ContinueSDK
 from .core.core import MessageStep
@@ -11,15 +7,10 @@ from .core.core import MessageStep
 
 class NLDecisionStep(Step):
     user_input: str
-<<<<<<< Updated upstream
-    steps: List[Step]
-    hide: bool = True
     default_step: Union[Step, None] = None
-=======
     steps: List[Tuple[Step, str]]
 
     hide: bool = True
->>>>>>> Stashed changes
 
     async def run(self, sdk: ContinueSDK):
         step_descriptions = "\n".join([
@@ -40,13 +31,8 @@ class NLDecisionStep(Step):
 
         step_to_run = None
         for step in self.steps:
-<<<<<<< Updated upstream
-            if step.name.lower() in resp:
-                step_to_run = step
-=======
             if step[0].name.lower() in resp:
                 step_to_run = step[0]
->>>>>>> Stashed changes
 
         step_to_run = step_to_run or self.default_step or self.steps[0]
 
-- 
cgit v1.2.3-70-g09d2


From af350f5e70f20d14c361684e361b1e64e5e0b2c3 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 12 Jun 2023 12:21:26 -0700
Subject: cleaner highlighting (from server side)

---
 .../src/continuedev/server/session_manager.py      |  1 +
 continuedev/src/continuedev/steps/core/core.py     | 45 +++++++++++++++++++---
 continuedev/src/continuedev/steps/main.py          | 28 +++++++++++---
 extension/src/continueIdeClient.ts                 | 31 +--------------
 4 files changed, 65 insertions(+), 40 deletions(-)

diff --git a/continuedev/src/continuedev/server/session_manager.py b/continuedev/src/continuedev/server/session_manager.py
index 0dbfaf38..ebea08a5 100644
--- a/continuedev/src/continuedev/server/session_manager.py
+++ b/continuedev/src/continuedev/server/session_manager.py
@@ -28,6 +28,7 @@ class DemoAutopilot(Autopilot):
     cumulative_edit_string = ""
 
     def handle_manual_edits(self, edits: List[FileEditWithFullContents]):
+        return
         for edit in edits:
             self.cumulative_edit_string += edit.fileEdit.replacement
             self._manual_edits_buffer.append(edit)
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 57689f19..4288ffd2 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -3,8 +3,10 @@ import os
 import subprocess
 from textwrap import dedent
 from typing import Coroutine, List, Union
-from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
 
+from ...models.main import Range
+from ...libs.util.calculate_diff import calculate_diff2, apply_edit_to_str
+from ...libs.llm.prompt_utils import MarkdownStyleEncoderDecoder
 from ...models.filesystem_edit import EditDiff, FileEdit, FileEditWithFullContents, FileSystemEdit
 from ...models.filesystem import FileSystem, RangeInFile, RangeInFileWithContents
 from ...core.observation import Observation, TextObservation, TracebackObservation, UserInputObservation
@@ -149,7 +151,11 @@ class Gpt35EditCodeStep(Step):
     _prompt_and_completion: str = ""
 
     async def describe(self, models: Models) -> Coroutine[str, None, None]:
-        return models.gpt35.complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:")
+        description = models.gpt35.complete(
+            f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points. Be concise and only mention changes made to the commit before, not prefix or suffix:")
+        self.name = models.gpt35.complete(
+            f"Write a short title for this description: {description}")
+        return description
 
     async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
         rif_with_contents = []
@@ -174,11 +180,40 @@ class Gpt35EditCodeStep(Step):
 
             self._prompt_and_completion += prompt + completion
 
-            await sdk.ide.applyFileSystemEdit(
-                FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion))
-            await sdk.ide.saveFile(rif.filepath)
+            # Calculate diff, open file, apply edits, and highlight changed lines
+            edits = calculate_diff2(
+                rif.filepath, rif.contents, completion.removesuffix("\n"))
+
             await sdk.ide.setFileOpen(rif.filepath)
 
+            lines_to_highlight = set()
+            for edit in edits:
+                edit.range.start.line += rif.range.start.line
+                edit.range.start.character += rif.range.start.character
+                edit.range.end.line += rif.range.start.line
+                edit.range.end.character += rif.range.start.character if edit.range.end.line == 0 else 0
+
+                for line in range(edit.range.start.line, edit.range.end.line + 1):
+                    lines_to_highlight.add(line)
+
+                await sdk.ide.applyFileSystemEdit(edit)
+
+            current_start = None
+            last_line = None
+            for line in sorted(list(lines_to_highlight)):
+                if current_start is None:
+                    current_start = line
+                elif line != last_line + 1:
+                    await sdk.ide.highlightCode(RangeInFile(filepath=edit.filepath, range=Range.from_shorthand(current_start, 0, last_line, 0)))
+                    current_start = line
+
+                last_line = line
+
+            if current_start is not None:
+                await sdk.ide.highlightCode(RangeInFile(filepath=edit.filepath, range=Range.from_shorthand(current_start, 0, last_line, 0)))
+
+            await sdk.ide.saveFile(rif.filepath)
+
 
 class EditFileStep(Step):
     filepath: str
diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py
index 24335b4f..9634c726 100644
--- a/continuedev/src/continuedev/steps/main.py
+++ b/continuedev/src/continuedev/steps/main.py
@@ -16,6 +16,7 @@ from ..core.sdk import ContinueSDK, Models
 from ..core.observation import Observation
 import subprocess
 from .core.core import Gpt35EditCodeStep
+from ..libs.util.calculate_diff import calculate_diff2
 
 
 class SetupContinueWorkspaceStep(Step):
@@ -216,7 +217,8 @@ class StarCoderEditHighlightedCodeStep(Step):
 
     async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
         range_in_files = await sdk.ide.getHighlightedCode()
-        if len(range_in_files) == 0:
+        found_highlighted_code = len(range_in_files) > 0
+        if not found_highlighted_code:
             # Get the full contents of all open files
             files = await sdk.ide.getOpenFiles()
             contents = {}
@@ -239,15 +241,29 @@ class StarCoderEditHighlightedCodeStep(Step):
         for rif in rif_with_contents:
             prompt = self._prompt.format(
                 code=rif.contents, user_request=self.user_input)
-            completion = str(sdk.models.starcoder.complete(prompt))
+
+            if found_highlighted_code:
+                full_file_contents = await sdk.ide.readFile(rif.filepath)
+                segs = full_file_contents.split(rif.contents)
+                prompt = f"<file_prefix>{segs[0]}<file_suffix>{segs[1]}" + prompt
+
+            completion = str((await sdk.models.starcoder()).complete(prompt))
             eot_token = "<|endoftext|>"
-            if completion.endswith(eot_token):
-                completion = completion[:completion.rindex(eot_token)]
+            completion = completion.removesuffix(eot_token)
+
+            if found_highlighted_code:
+                rif.contents = segs[0] + rif.contents + segs[1]
+                completion = segs[0] + completion + segs[1]
 
             self._prompt_and_completion += prompt + completion
 
-            await sdk.ide.applyFileSystemEdit(
-                FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion))
+            edits = calculate_diff2(
+                rif.filepath, rif.contents, completion.removesuffix("\n"))
+            for edit in edits:
+                await sdk.ide.applyFileSystemEdit(edit)
+
+            # await sdk.ide.applyFileSystemEdit(
+            #     FileEdit(filepath=rif.filepath, range=rif.range, replacement=completion))
             await sdk.ide.saveFile(rif.filepath)
             await sdk.ide.setFileOpen(rif.filepath)
 
diff --git a/extension/src/continueIdeClient.ts b/extension/src/continueIdeClient.ts
index c395ae0e..035778a5 100644
--- a/extension/src/continueIdeClient.ts
+++ b/extension/src/continueIdeClient.ts
@@ -278,40 +278,13 @@ class IdeProtocolClient {
         undefined,
         vscode.ViewColumn.One
       ).then((editor) => {
-        let range = new vscode.Range(
+        const range = new vscode.Range(
           edit.range.start.line,
           edit.range.start.character,
           edit.range.end.line,
           edit.range.end.character
         );
-        const decorationKey =
-          edit.replacement === ""
-            ? {
-                editorUri: editor.document.uri.fsPath,
-                options: {
-                  range: new vscode.Range(
-                    new vscode.Position(range.start.line, 0),
-                    new vscode.Position(range.end.line + 1, 0)
-                  ),
-                  // after: {
-                  //   contentText: "Removed",
-                  // },
-                },
-                decorationType: vscode.window.createTextEditorDecorationType({
-                  backgroundColor: "rgba(255, 0, 0, 0.2)",
-                }),
-              }
-            : {
-                editorUri: editor.document.uri.fsPath,
-                options: {
-                  range,
-                },
-                decorationType: vscode.window.createTextEditorDecorationType({
-                  backgroundColor: "rgba(66, 105, 55, 1.0)",
-                  isWholeLine: true,
-                }),
-              };
-        decorationManager.addDecoration(decorationKey);
+
         editor.edit((editBuilder) => {
           this._makingEdit += 2; // editBuilder.replace takes 2 edits: delete and insert
           editBuilder.replace(range, edit.replacement);
-- 
cgit v1.2.3-70-g09d2


From 25262703573e79fc436c32f48b8df428bfeb4c97 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 12 Jun 2023 13:18:16 -0700
Subject: clear history and delete step buttons

---
 continuedev/src/continuedev/core/autopilot.py      |  10 +
 .../src/continuedev/libs/util/calculate_diff.py    |  14 ++
 continuedev/src/continuedev/server/gui.py          |  10 +
 continuedev/src/continuedev/server/gui_protocol.py |   8 +
 continuedev/src/continuedev/steps/core/core.py     |   2 +-
 .../react-app/src/components/StepContainer.tsx     |  44 ++--
 extension/react-app/src/components/index.ts        |  18 ++
 .../src/hooks/ContinueGUIClientProtocol.ts         |   6 +
 .../react-app/src/hooks/useContinueGUIProtocol.ts  |   8 +
 extension/react-app/src/tabs/gui.tsx               | 272 +++++++++++----------
 10 files changed, 245 insertions(+), 147 deletions(-)

diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index c979d53a..1642003c 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -40,6 +40,12 @@ class Autopilot(ContinueBaseModel):
     def get_full_state(self) -> FullState:
         return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue)
 
+    async def clear_history(self):
+        self.history = History.from_empty()
+        self._main_user_input_queue = []
+        self._active = False
+        await self.update_subscribers()
+
     def on_update(self, callback: Coroutine["FullState", None, None]):
         """Subscribe to changes to state"""
         self._on_update_callbacks.append(callback)
@@ -88,6 +94,10 @@ class Autopilot(ContinueBaseModel):
     async def retry_at_index(self, index: int):
         self._retry_queue.post(str(index), None)
 
+    async def delete_at_index(self, index: int):
+        self.history.timeline[index].step.hide = True
+        await self.update_subscribers()
+
     async def _run_singular_step(self, step: "Step", is_future_step: bool = False) -> Coroutine[Observation, None, None]:
         capture_event(
             'step run', {'step_name': step.name, 'params': step.dict()})
diff --git a/continuedev/src/continuedev/libs/util/calculate_diff.py b/continuedev/src/continuedev/libs/util/calculate_diff.py
index d778891b..ff0a135f 100644
--- a/continuedev/src/continuedev/libs/util/calculate_diff.py
+++ b/continuedev/src/continuedev/libs/util/calculate_diff.py
@@ -67,6 +67,20 @@ def calculate_diff(filepath: str, original: str, updated: str) -> List[FileEdit]
 
 
 def calculate_diff2(filepath: str, original: str, updated: str) -> List[FileEdit]:
+    # original_lines = original.splitlines()
+    # updated_lines = updated.splitlines()
+    # offset = 0
+    # while len(original_lines) and len(updated_lines) and original_lines[0] == updated_lines[0]:
+    #     original_lines = original_lines[1:]
+    #     updated_lines = updated_lines[1:]
+
+    # while len(original_lines) and len(updated_lines) and original_lines[-1] == updated_lines[-1]:
+    #     original_lines = original_lines[:-1]
+    #     updated_lines = updated_lines[:-1]
+
+    # original = "\n".join(original_lines)
+    # updated = "\n".join(updated_lines)
+
     edits = []
     max_iterations = 1000
     i = 0
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index b873a88f..e8b52004 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -77,6 +77,10 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
                 self.on_reverse_to_index(data["index"])
             elif message_type == "retry_at_index":
                 self.on_retry_at_index(data["index"])
+            elif message_type == "clear_history":
+                self.on_clear_history()
+            elif message_type == "delete_at_index":
+                self.on_delete_at_index(data["index"])
         except Exception as e:
             print(e)
 
@@ -106,6 +110,12 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
         asyncio.create_task(
             self.session.autopilot.retry_at_index(index))
 
+    def on_clear_history(self):
+        asyncio.create_task(self.session.autopilot.clear_history())
+
+    def on_delete_at_index(self, index: int):
+        asyncio.create_task(self.session.autopilot.delete_at_index(index))
+
 
 @router.websocket("/ws")
 async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(websocket_session)):
diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py
index 287f9e3b..889c6761 100644
--- a/continuedev/src/continuedev/server/gui_protocol.py
+++ b/continuedev/src/continuedev/server/gui_protocol.py
@@ -30,3 +30,11 @@ class AbstractGUIProtocolServer(ABC):
     @abstractmethod
     def on_retry_at_index(self, index: int):
         """Called when the user requests a retry at a previous index"""
+
+    @abstractmethod
+    def on_clear_history(self):
+        """Called when the user requests to clear the history"""
+
+    @abstractmethod
+    def on_delete_at_index(self, index: int):
+        """Called when the user requests to delete a step at a given index"""
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index 4288ffd2..dacf0e7b 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -193,7 +193,7 @@ class Gpt35EditCodeStep(Step):
                 edit.range.end.line += rif.range.start.line
                 edit.range.end.character += rif.range.start.character if edit.range.end.line == 0 else 0
 
-                for line in range(edit.range.start.line, edit.range.end.line + 1):
+                for line in range(edit.range.start.line, edit.range.end.line + 1 + len(edit.replacement.splitlines()) - (edit.range.end.line - edit.range.start.line + 1)):
                     lines_to_highlight.add(line)
 
                 await sdk.ide.applyFileSystemEdit(edit)
diff --git a/extension/react-app/src/components/StepContainer.tsx b/extension/react-app/src/components/StepContainer.tsx
index fb0143b5..2d85b4f0 100644
--- a/extension/react-app/src/components/StepContainer.tsx
+++ b/extension/react-app/src/components/StepContainer.tsx
@@ -7,6 +7,7 @@ import {
   vscBackground,
   GradientBorder,
   vscBackgroundTransparent,
+  HeaderButton,
 } from ".";
 import { RangeInFile, FileEdit } from "../../../src/client";
 import CodeBlock from "./CodeBlock";
@@ -15,7 +16,7 @@ import SubContainer from "./SubContainer";
 import {
   ChevronDown,
   ChevronRight,
-  Backward,
+  XMark,
   ArrowPath,
 } from "@styled-icons/heroicons-outline";
 import { HistoryNode } from "../../../schema/HistoryNode";
@@ -31,6 +32,7 @@ interface StepContainerProps {
   onRefinement: (input: string) => void;
   onUserInput: (input: string) => void;
   onRetry: () => void;
+  onDelete: () => void;
   open?: boolean;
 }
 
@@ -54,8 +56,10 @@ const HeaderDiv = styled.div<{ error: boolean }>`
   background-color: ${(props) =>
     props.error ? "#522" : vscBackgroundTransparent};
   display: grid;
-  grid-template-columns: 1fr auto;
+  grid-template-columns: 1fr auto auto;
+  grid-gap: 8px;
   align-items: center;
+  padding-right: 8px;
 `;
 
 const ContentDiv = styled.div`
@@ -64,20 +68,6 @@ const ContentDiv = styled.div`
   background-color: ${vscBackground};
 `;
 
-const HeaderButton = styled.button`
-  background-color: transparent;
-  border: 1px solid white;
-  border-radius: ${defaultBorderRadius};
-  padding: 2px;
-  cursor: pointer;
-  color: white;
-
-  &:hover {
-    background-color: white;
-    color: black;
-  }
-`;
-
 const OnHoverDiv = styled.div`
   text-align: center;
   padding: 10px;
@@ -161,18 +151,28 @@ function StepContainer(props: StepContainerProps) {
               <Backward size="1.6em" onClick={props.onReverse}></Backward>
             </HeaderButton> */}
 
-            {props.historyNode.observation?.error ? (
+            <>
               <HeaderButton
                 onClick={(e) => {
                   e.stopPropagation();
-                  props.onRetry();
+                  props.onDelete();
                 }}
               >
-                <ArrowPath size="1.6em" onClick={props.onRetry}></ArrowPath>
+                <XMark size="1.6em" onClick={props.onDelete} />
               </HeaderButton>
-            ) : (
-              <></>
-            )}
+              {props.historyNode.observation?.error ? (
+                <HeaderButton
+                  onClick={(e) => {
+                    e.stopPropagation();
+                    props.onRetry();
+                  }}
+                >
+                  <ArrowPath size="1.6em" onClick={props.onRetry} />
+                </HeaderButton>
+              ) : (
+                <></>
+              )}
+            </>
           </HeaderDiv>
         </GradientBorder>
         <ContentDiv hidden={!open}>
diff --git a/extension/react-app/src/components/index.ts b/extension/react-app/src/components/index.ts
index 4966f3e8..525989af 100644
--- a/extension/react-app/src/components/index.ts
+++ b/extension/react-app/src/components/index.ts
@@ -143,3 +143,21 @@ export const appear = keyframes`
         transform: translateY(0px);
     }
 `;
+
+export const HeaderButton = styled.button`
+  background-color: transparent;
+  border: 1px solid white;
+  border-radius: ${defaultBorderRadius};
+  cursor: pointer;
+  color: white;
+
+  &:hover {
+    background-color: white;
+    color: black;
+  }
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 4px;
+  padding: 1px;
+`;
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index 18a91de7..71303c70 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -8,6 +8,12 @@ abstract class AbstractContinueGUIClientProtocol {
   abstract sendStepUserInput(input: string, index: number): void;
 
   abstract onStateUpdate(state: any): void;
+
+  abstract sendClear(): void;
+
+  abstract retryAtIndex(index: number): void;
+
+  abstract deleteAtIndex(index: number): void;
 }
 
 export default AbstractContinueGUIClientProtocol;
diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
index f27895fb..a8e28fc5 100644
--- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts
+++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
@@ -45,9 +45,17 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
     });
   }
 
+  sendClear() {
+    this.messenger.send("clear_history", {});
+  }
+
   retryAtIndex(index: number) {
     this.messenger.send("retry_at_index", { index });
   }
+
+  deleteAtIndex(index: number) {
+    this.messenger.send("delete_at_index", { index });
+  }
 }
 
 export default ContinueGUIClientProtocol;
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index 9f7e651f..cb7a5440 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -4,6 +4,7 @@ import {
   vscBackground,
   Loader,
   MainTextInput,
+  HeaderButton,
 } from "../components";
 import ContinueButton from "../components/ContinueButton";
 import { useCallback, useEffect, useRef, useState } from "react";
@@ -11,7 +12,7 @@ import { History } from "../../../schema/History";
 import { HistoryNode } from "../../../schema/HistoryNode";
 import StepContainer from "../components/StepContainer";
 import useContinueGUIProtocol from "../hooks/useWebsocket";
-
+import { Trash } from "@styled-icons/heroicons-outline";
 let TopGUIDiv = styled.div`
   display: grid;
   grid-template-columns: 1fr;
@@ -26,6 +27,14 @@ let UserInputQueueItem = styled.div`
   text-align: center;
 `;
 
+const TopBar = styled.div`
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 8px;
+  align-items: center;
+`;
+
 interface GUIProps {
   firstObservation?: any;
 }
@@ -33,129 +42,128 @@ interface GUIProps {
 function GUI(props: GUIProps) {
   const [waitingForSteps, setWaitingForSteps] = useState(false);
   const [userInputQueue, setUserInputQueue] = useState<string[]>([]);
-  const [history, setHistory] = useState<History | undefined>();
-  //   {
-  //   timeline: [
-  //     {
-  //       step: {
-  //         name: "Waiting for user input",
-  //         cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-  //         description:
-  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
-  //       },
-  //       observation: {
-  //         title: "ERROR FOUND",
-  //         error:
-  //           "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
-  //       },
-  //       output: [
-  //         {
-  //           traceback: {
-  //             frames: [
-  //               {
-  //                 filepath:
-  //                   "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-  //                 lineno: 7,
-  //                 function: "<module>",
-  //                 code: "print(sum(first, second))",
-  //               },
-  //             ],
-  //             message: "unsupported operand type(s) for +: 'int' and 'str'",
-  //             error_type:
-  //               '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
-  //             full_traceback:
-  //               "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
-  //           },
-  //         },
-  //         null,
-  //       ],
-  //     },
-  //     {
-  //       step: {
-  //         name: "EditCodeStep",
-  //         range_in_files: [
-  //           {
-  //             filepath:
-  //               "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-  //             range: {
-  //               start: {
-  //                 line: 0,
-  //                 character: 0,
-  //               },
-  //               end: {
-  //                 line: 6,
-  //                 character: 25,
-  //               },
-  //             },
-  //           },
-  //         ],
-  //         prompt:
-  //           "I ran into this problem with my Python code:\n\n                Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n                Below are the files that might need to be fixed:\n\n                {code}\n\n                This is what the code should be in order to avoid the problem:\n",
-  //         description:
-  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
-  //       },
-  //       output: [
-  //         null,
-  //         {
-  //           reversible: true,
-  //           actions: [
-  //             {
-  //               reversible: true,
-  //               filesystem: {},
-  //               filepath:
-  //                 "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-  //               range: {
-  //                 start: {
-  //                   line: 0,
-  //                   character: 0,
-  //                 },
-  //                 end: {
-  //                   line: 6,
-  //                   character: 25,
-  //                 },
-  //               },
-  //               replacement:
-  //                 "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))",
-  //             },
-  //           ],
-  //         },
-  //       ],
-  //     },
-  //     {
-  //       step: {
-  //         name: "SolveTracebackStep",
-  //         traceback: {
-  //           frames: [
-  //             {
-  //               filepath:
-  //                 "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-  //               lineno: 7,
-  //               function: "<module>",
-  //               code: "print(sum(first, second))",
-  //             },
-  //           ],
-  //           message: "unsupported operand type(s) for +: 'int' and 'str'",
-  //           error_type:
-  //             '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
-  //           full_traceback:
-  //             "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
-  //         },
-  //         description: "Running step: SolveTracebackStep",
-  //       },
-  //       output: [null, null],
-  //     },
-  //     {
-  //       step: {
-  //         name: "RunCodeStep",
-  //         cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-  //         description:
-  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`",
-  //       },
-  //       output: [null, null],
-  //     },
-  //   ],
-  //   current_index: 3,
-  // } as any);
+  const [history, setHistory] = useState<History | undefined>({
+    timeline: [
+      {
+        step: {
+          name: "Waiting for user input",
+          cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+          description:
+            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
+        },
+        observation: {
+          title: "ERROR FOUND",
+          error:
+            "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
+        },
+        output: [
+          {
+            traceback: {
+              frames: [
+                {
+                  filepath:
+                    "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+                  lineno: 7,
+                  function: "<module>",
+                  code: "print(sum(first, second))",
+                },
+              ],
+              message: "unsupported operand type(s) for +: 'int' and 'str'",
+              error_type:
+                '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
+              full_traceback:
+                "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
+            },
+          },
+          null,
+        ],
+      },
+      {
+        step: {
+          name: "EditCodeStep",
+          range_in_files: [
+            {
+              filepath:
+                "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+              range: {
+                start: {
+                  line: 0,
+                  character: 0,
+                },
+                end: {
+                  line: 6,
+                  character: 25,
+                },
+              },
+            },
+          ],
+          prompt:
+            "I ran into this problem with my Python code:\n\n                Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n                Below are the files that might need to be fixed:\n\n                {code}\n\n                This is what the code should be in order to avoid the problem:\n",
+          description:
+            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
+        },
+        output: [
+          null,
+          {
+            reversible: true,
+            actions: [
+              {
+                reversible: true,
+                filesystem: {},
+                filepath:
+                  "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+                range: {
+                  start: {
+                    line: 0,
+                    character: 0,
+                  },
+                  end: {
+                    line: 6,
+                    character: 25,
+                  },
+                },
+                replacement:
+                  "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))",
+              },
+            ],
+          },
+        ],
+      },
+      {
+        step: {
+          name: "SolveTracebackStep",
+          traceback: {
+            frames: [
+              {
+                filepath:
+                  "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+                lineno: 7,
+                function: "<module>",
+                code: "print(sum(first, second))",
+              },
+            ],
+            message: "unsupported operand type(s) for +: 'int' and 'str'",
+            error_type:
+              '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
+            full_traceback:
+              "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
+          },
+          description: "Running step: SolveTracebackStep",
+        },
+        output: [null, null],
+      },
+      {
+        step: {
+          name: "RunCodeStep",
+          cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+          description:
+            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`",
+        },
+        output: [null, null],
+      },
+    ],
+    current_index: 3,
+  } as any);
 
   const topGuiDivRef = useRef<HTMLDivElement>(null);
   const client = useContinueGUIProtocol();
@@ -261,6 +269,19 @@ function GUI(props: GUIProps) {
         }
       }}
     >
+      <TopBar>
+        <h3>Continue</h3>
+        <HeaderButton style={{ padding: "3px" }}>
+          Clear History
+          <Trash
+            size="1.6em"
+            onClick={() => {
+              client?.sendClear();
+            }}
+          />
+        </HeaderButton>
+      </TopBar>
+
       {typeof client === "undefined" && (
         <>
           <Loader></Loader>
@@ -288,6 +309,9 @@ function GUI(props: GUIProps) {
               client?.retryAtIndex(index);
               setWaitingForSteps(true);
             }}
+            onDelete={() => {
+              client?.deleteAtIndex(index);
+            }}
           />
         );
       })}
-- 
cgit v1.2.3-70-g09d2


From cc0d73c2c1351a08c95654f7792f56c1d3d0ab54 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 12 Jun 2023 21:30:49 -0700
Subject: slash commands dropdown!

---
 continuedev/src/continuedev/core/abstract_sdk.py   |   4 +-
 continuedev/src/continuedev/core/autopilot.py      |   9 +-
 continuedev/src/continuedev/core/config.py         |  34 ++-
 continuedev/src/continuedev/core/main.py           |   6 +-
 continuedev/src/continuedev/core/policy.py         |  73 +----
 continuedev/src/continuedev/core/sdk.py            |   6 +-
 .../continuedev/libs/util/step_name_to_steps.py    |  27 ++
 .../recipes/CreatePipelineRecipe/main.py           |   2 +-
 .../recipes/CreatePipelineRecipe/steps.py          |   3 +-
 continuedev/src/continuedev/server/gui.py          |   7 +
 continuedev/src/continuedev/server/gui_protocol.py |   6 +-
 continuedev/src/continuedev/steps/main.py          |   4 +-
 .../src/continuedev/steps/steps_on_startup.py      |  24 +-
 extension/package-lock.json                        |  64 ++++-
 extension/package.json                             |   3 +-
 extension/react-app/package-lock.json              |  60 +++++
 extension/react-app/package.json                   |   1 +
 extension/react-app/src/components/ComboBox.tsx    | 146 ++++++++++
 .../src/hooks/ContinueGUIClientProtocol.ts         |   4 +
 .../react-app/src/hooks/useContinueGUIProtocol.ts  |  10 +
 extension/react-app/src/tabs/gui.tsx               | 296 +++++++++++----------
 .../scripts/continuedev-0.1.1-py3-none-any.whl     | Bin 74791 -> 78916 bytes
 22 files changed, 547 insertions(+), 242 deletions(-)
 create mode 100644 continuedev/src/continuedev/libs/util/step_name_to_steps.py
 create mode 100644 extension/react-app/src/components/ComboBox.tsx

diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py
index 3b85708d..0658f1b8 100644
--- a/continuedev/src/continuedev/core/abstract_sdk.py
+++ b/continuedev/src/continuedev/core/abstract_sdk.py
@@ -76,8 +76,8 @@ class AbstractContinueSDK(ABC):
     async def get_user_secret(self, env_var: str, prompt: str) -> str:
         pass
 
-    @abstractmethod
-    async def get_config(self) -> ContinueConfig:
+    @abstractproperty
+    def config(self) -> ContinueConfig:
         pass
 
     @abstractmethod
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index 1642003c..0874bbc5 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -40,6 +40,9 @@ class Autopilot(ContinueBaseModel):
     def get_full_state(self) -> FullState:
         return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue)
 
+    async def get_available_slash_commands(self) -> List[Dict]:
+        return list(map(lambda x: {"name": x.name, "description": x.description}, self.continue_sdk.config.slash_commands)) or []
+
     async def clear_history(self):
         self.history = History.from_empty()
         self._main_user_input_queue = []
@@ -202,7 +205,7 @@ class Autopilot(ContinueBaseModel):
 
             await self._run_singular_step(next_step, is_future_step)
 
-            if next_step := self.policy.next(self.history):
+            if next_step := self.policy.next(self.continue_sdk.config, self.history):
                 is_future_step = False
             elif next_step := self.history.take_next_step():
                 is_future_step = True
@@ -215,11 +218,11 @@ class Autopilot(ContinueBaseModel):
         await self.update_subscribers()
 
     async def run_from_observation(self, observation: Observation):
-        next_step = self.policy.next(self.history)
+        next_step = self.policy.next(self.continue_sdk.config, self.history)
         await self.run_from_step(next_step)
 
     async def run_policy(self):
-        first_step = self.policy.next(self.history)
+        first_step = self.policy.next(self.continue_sdk.config, self.history)
         await self.run_from_step(first_step)
 
     async def _request_halt(self):
diff --git a/continuedev/src/continuedev/core/config.py b/continuedev/src/continuedev/core/config.py
index 8ed41a82..cf723984 100644
--- a/continuedev/src/continuedev/core/config.py
+++ b/continuedev/src/continuedev/core/config.py
@@ -1,9 +1,18 @@
 import json
 import os
-from pydantic import BaseModel
+from pydantic import BaseModel, validator
 from typing import List, Optional, Dict
 import yaml
 
+from .main import Step
+
+
+class SlashCommand(BaseModel):
+    name: str
+    description: str
+    step_name: str
+    params: Optional[Dict] = {}
+
 
 class ContinueConfig(BaseModel):
     """
@@ -12,6 +21,29 @@ class ContinueConfig(BaseModel):
     steps_on_startup: Optional[Dict[str, Dict]] = {}
     server_url: Optional[str] = None
     allow_anonymous_telemetry: Optional[bool] = True
+    slash_commands: Optional[List[SlashCommand]] = [
+        # SlashCommand(
+        #     name="pytest",
+        #     description="Write pytest unit tests for the current file",
+        #     step_name="WritePytestsRecipe",
+        #     params=??)
+
+        SlashCommand(
+            name="dlt",
+            description="Create a dlt pipeline",
+            step_name="CreatePipelineRecipe",
+        ),
+        SlashCommand(
+            name="ddtobq",
+            description="Create a dlt pipeline to load data from a data source into BigQuery",
+            step_name="DDtoBQRecipe",
+        ),
+        SlashCommand(
+            name="deployairflow",
+            description="Deploy a dlt pipeline to Airflow",
+            step_name="DeployPipelineAirflowRecipe",
+        ),
+    ]
 
 
 def load_config(config_file: str) -> ContinueConfig:
diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index 81aaaf2e..f6b26d69 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -118,11 +118,15 @@ class Models:
     pass
 
 
+class ContinueConfig:
+    pass
+
+
 class Policy(ContinueBaseModel):
     """A rule that determines which step to take next"""
 
     # Note that history is mutable, kinda sus
-    def next(self, history: History = History.from_empty()) -> "Step":
+    def next(self, config: ContinueConfig, history: History = History.from_empty()) -> "Step":
         raise NotImplementedError
 
 
diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index 00b5427c..37a10e36 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -1,5 +1,6 @@
 from typing import List, Tuple, Type
 
+from .config import ContinueConfig
 from ..steps.chroma import AnswerQuestionChroma, EditFileChroma, CreateCodebaseIndexChroma
 from ..steps.steps_on_startup import StepsOnStartupStep
 from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
@@ -15,12 +16,13 @@ from ..steps.react import NLDecisionStep
 from ..steps.chat import SimpleChatStep
 from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe
 from ..steps.core.core import MessageStep
+from ..libs.util.step_name_to_steps import get_step_from_name
 
 
 class DemoPolicy(Policy):
     ran_code_last: bool = False
 
-    def next(self, history: History) -> Step:
+    def next(self, config: ContinueConfig, history: History) -> Step:
         # At the very start, run initial Steps spcecified in the config
         if history.get_current() is None:
             return (
@@ -33,20 +35,18 @@ class DemoPolicy(Policy):
         if observation is not None and isinstance(observation, UserInputObservation):
             # This could be defined with ObservationTypePolicy. Ergonomics not right though.
             user_input = observation.user_input
+
+            if user_input.startswith("/"):
+                command_name = user_input.split(" ")[0]
+                after_command = " ".join(user_input.split(" ")[1:])
+                for slash_command in config.slash_commands:
+                    if slash_command.name == command_name[1:]:
+                        return get_step_from_name(slash_command.step_name, slash_command.params)
+
             if "/pytest" in user_input.lower():
                 return WritePytestsRecipe(instructions=user_input)
-            elif "/dlt" in user_input.lower() or " dlt" in user_input.lower():
-                return CreatePipelineRecipe()
             if "/pytest" in observation.user_input.lower():
                 return WritePytestsRecipe(instructions=observation.user_input)
-            elif "/dlt" in observation.user_input.lower():
-                return CreatePipelineRecipe()
-            elif "/ddtobq" in observation.user_input.lower():
-                return DDtoBQRecipe()
-            elif "/airflow" in observation.user_input.lower():
-                return DeployPipelineAirflowRecipe()
-            elif "/transform" in observation.user_input.lower():
-                return AddTransformRecipe()
             elif "/comment" in observation.user_input.lower():
                 return CommentCodeStep()
             elif "/ask" in user_input:
@@ -72,54 +72,3 @@ class DemoPolicy(Policy):
             return SolveTracebackStep(traceback=observation.traceback)
         else:
             return None
-
-
-class ObservationTypePolicy(Policy):
-    def __init__(self, base_policy: Policy, observation_type: Type[Observation], step_type: Type[Step]):
-        self.observation_type = observation_type
-        self.step_type = step_type
-        self.base_policy = base_policy
-
-    def next(self, history: History) -> Step:
-        observation = history.last_observation()
-        if observation is not None and isinstance(observation, self.observation_type):
-            return self.step_type(observation)
-        return self.base_policy.next(history)
-
-
-class PolicyWrappedWithValidators(Policy):
-    """Default is to stop, unless the validator tells what to do next"""
-    index: int
-    stage: int
-
-    def __init__(self, base_policy: Policy, pairs: List[Tuple[Validator, Type[Step]]]):
-        # Want to pass Type[Validator], or just the Validator? Question of where params are coming from.
-        self.pairs = pairs
-        self.index = len(pairs)
-        self.validating = 0
-        self.base_policy = base_policy
-
-    def next(self, history: History) -> Step:
-        if self.index == len(self.pairs):
-            self.index = 0
-            return self.base_policy.next(history)
-
-        if self.stage == 0:
-            # Running the validator at the current index for the first time
-            validator, step = self.pairs[self.index]
-            self.stage = 1
-            return validator
-        elif self.stage == 1:
-            # Previously ran the validator at the current index, now receiving its ValidatorObservation
-            observation = history.last_observation()
-            if observation.passed:
-                self.stage = 0
-                self.index += 1
-                if self.index == len(self.pairs):
-                    self.index = 0
-                    return self.base_policy.next(history)
-                else:
-                    return self.pairs[self.index][0]
-            else:
-                _, step_type = self.pairs[self.index]
-                return step_type(observation)
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index 2849b0c8..1f4cdfb2 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -1,4 +1,3 @@
-from abc import ABC, abstractmethod
 import asyncio
 from functools import cached_property
 from typing import Coroutine, Union
@@ -119,8 +118,9 @@ class ContinueSDK(AbstractContinueSDK):
     async def get_user_secret(self, env_var: str, prompt: str) -> str:
         return await self.ide.getUserSecret(env_var)
 
-    async def get_config(self) -> ContinueConfig:
-        dir = await self.ide.getWorkspaceDirectory()
+    @property
+    def config(self) -> ContinueConfig:
+        dir = self.ide.workspace_directory
         yaml_path = os.path.join(dir, '.continue', 'config.yaml')
         json_path = os.path.join(dir, '.continue', 'config.json')
         if os.path.exists(yaml_path):
diff --git a/continuedev/src/continuedev/libs/util/step_name_to_steps.py b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
new file mode 100644
index 00000000..4023b73b
--- /dev/null
+++ b/continuedev/src/continuedev/libs/util/step_name_to_steps.py
@@ -0,0 +1,27 @@
+from typing import Dict
+
+from ...core.main import Step
+from ...steps.core.core import UserInputStep
+from ...recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
+from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe
+from ...recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe
+from ...recipes.DDtoBQRecipe.main import DDtoBQRecipe
+from ...recipes.AddTransformRecipe.main import AddTransformRecipe
+
+step_name_to_step_class = {
+    "UserInputStep": UserInputStep,
+    "CreatePipelineRecipe": CreatePipelineRecipe,
+    "DDtoBQRecipe": DDtoBQRecipe,
+    "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe,
+    "AddTransformRecipe": AddTransformRecipe,
+    "DDtoBQRecipe": DDtoBQRecipe
+}
+
+
+def get_step_from_name(step_name: str, params: Dict) -> Step:
+    try:
+        return step_name_to_step_class[step_name](**params)
+    except:
+        print(
+            f"Incorrect parameters for step {step_name}. Parameters provided were: {params}")
+        raise
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
index 818168ba..92bddc98 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
+++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
@@ -1,7 +1,7 @@
 from textwrap import dedent
 
-from ...core.main import Step
 from ...core.sdk import ContinueSDK
+from ...core.main import Step
 from ...steps.core.core import WaitForUserInputStep
 from ...steps.core.core import MessageStep
 from .steps import SetupPipelineStep, ValidatePipelineStep, RunQueryStep
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
index e59cc51c..ea4607da 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
+++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
@@ -6,11 +6,10 @@ import time
 from ...models.main import Range
 from ...models.filesystem import RangeInFile
 from ...steps.core.core import MessageStep
-from ...core.sdk import Models
 from ...core.observation import DictObservation, InternalErrorObservation
 from ...models.filesystem_edit import AddFile, FileEdit
 from ...core.main import Step
-from ...core.sdk import ContinueSDK
+from ...core.sdk import ContinueSDK, Models
 
 AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)"
 
diff --git a/continuedev/src/continuedev/server/gui.py b/continuedev/src/continuedev/server/gui.py
index e8b52004..cf046734 100644
--- a/continuedev/src/continuedev/server/gui.py
+++ b/continuedev/src/continuedev/server/gui.py
@@ -90,6 +90,12 @@ class GUIProtocolServer(AbstractGUIProtocolServer):
             "state": state
         })
 
+    async def send_available_slash_commands(self):
+        commands = await self.session.autopilot.get_available_slash_commands()
+        await self._send_json("available_slash_commands", {
+            "commands": commands
+        })
+
     def on_main_input(self, input: str):
         # Do something with user input
         asyncio.create_task(self.session.autopilot.accept_user_input(input))
@@ -127,6 +133,7 @@ async def websocket_endpoint(websocket: WebSocket, session: Session = Depends(we
     protocol.websocket = websocket
 
     # Update any history that may have happened before connection
+    await protocol.send_available_slash_commands()
     await protocol.send_state_update()
 
     while AppStatus.should_exit is False:
diff --git a/continuedev/src/continuedev/server/gui_protocol.py b/continuedev/src/continuedev/server/gui_protocol.py
index 889c6761..d9506c6f 100644
--- a/continuedev/src/continuedev/server/gui_protocol.py
+++ b/continuedev/src/continuedev/server/gui_protocol.py
@@ -1,4 +1,4 @@
-from typing import Any
+from typing import Any, Dict, List
 from abc import ABC, abstractmethod
 
 
@@ -27,6 +27,10 @@ class AbstractGUIProtocolServer(ABC):
     async def send_state_update(self, state: dict):
         """Send a state update to the client"""
 
+    @abstractmethod
+    async def send_available_slash_commands(self, commands: List[Dict]):
+        """Send a list of available slash commands to the client"""
+
     @abstractmethod
     def on_retry_at_index(self, index: int):
         """Called when the user requests a retry at a previous index"""
diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py
index 9634c726..36e4f519 100644
--- a/continuedev/src/continuedev/steps/main.py
+++ b/continuedev/src/continuedev/steps/main.py
@@ -63,10 +63,10 @@ class RunPolicyUntilDoneStep(Step):
     policy: "Policy"
 
     async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
-        next_step = self.policy.next(sdk.history)
+        next_step = self.policy.next(sdk.config, sdk.history)
         while next_step is not None:
             observation = await sdk.run_step(next_step)
-            next_step = self.policy.next(sdk.history)
+            next_step = self.policy.next(sdk.config, sdk.history)
         return observation
 
 
diff --git a/continuedev/src/continuedev/steps/steps_on_startup.py b/continuedev/src/continuedev/steps/steps_on_startup.py
index eae8b558..365cbe1a 100644
--- a/continuedev/src/continuedev/steps/steps_on_startup.py
+++ b/continuedev/src/continuedev/steps/steps_on_startup.py
@@ -1,19 +1,12 @@
-from ..core.main import ContinueSDK, Models, Step
+from ..core.main import Step
+from ..core.sdk import Models, ContinueSDK
 from .main import UserInputStep
 from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
 from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe
 from ..recipes.DeployPipelineAirflowRecipe.main import DeployPipelineAirflowRecipe
 from ..recipes.DDtoBQRecipe.main import DDtoBQRecipe
 from ..recipes.AddTransformRecipe.main import AddTransformRecipe
-
-step_name_to_step_class = {
-    "UserInputStep": UserInputStep,
-    "CreatePipelineRecipe": CreatePipelineRecipe,
-    "DDtoBQRecipe": DDtoBQRecipe,
-    "DeployPipelineAirflowRecipe": DeployPipelineAirflowRecipe,
-    "AddTransformRecipe": AddTransformRecipe,
-    "DDtoBQRecipe": DDtoBQRecipe
-}
+from ..libs.util.step_name_to_steps import get_step_from_name
 
 
 class StepsOnStartupStep(Step):
@@ -23,13 +16,8 @@ class StepsOnStartupStep(Step):
         return "Running steps on startup"
 
     async def run(self, sdk: ContinueSDK):
-        steps_descriptions = (await sdk.get_config()).steps_on_startup
+        steps_on_startup = sdk.config.steps_on_startup
 
-        for step_name, step_params in steps_descriptions.items():
-            try:
-                step = step_name_to_step_class[step_name](**step_params)
-            except:
-                print(
-                    f"Incorrect parameters for step {step_name}. Parameters provided were: {step_params}")
-                continue
+        for step_name, step_params in steps_on_startup.items():
+            step = get_step_from_name(step_name, step_params)
             await sdk.run_step(step)
diff --git a/extension/package-lock.json b/extension/package-lock.json
index aebd0803..b02c4544 100644
--- a/extension/package-lock.json
+++ b/extension/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "continue",
-  "version": "0.0.34",
+  "version": "0.0.35",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "continue",
-      "version": "0.0.34",
+      "version": "0.0.35",
       "license": "Apache-2.0",
       "dependencies": {
         "@electron/rebuild": "^3.2.10",
@@ -15,6 +15,7 @@
         "@styled-icons/heroicons-outline": "^10.47.0",
         "@vitejs/plugin-react-swc": "^3.3.2",
         "axios": "^1.2.5",
+        "downshift": "^7.6.0",
         "highlight.js": "^11.7.0",
         "posthog-js": "^1.63.3",
         "react-markdown": "^8.0.7",
@@ -2845,6 +2846,11 @@
       "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==",
       "dev": true
     },
+    "node_modules/compute-scroll-into-view": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
+      "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
+    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -3261,6 +3267,31 @@
         "url": "https://github.com/fb55/domutils?sponsor=1"
       }
     },
+    "node_modules/downshift": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz",
+      "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==",
+      "dependencies": {
+        "@babel/runtime": "^7.14.8",
+        "compute-scroll-into-view": "^2.0.4",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.12.0"
+      }
+    },
+    "node_modules/downshift/node_modules/react-is": {
+      "version": "17.0.2",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+    },
+    "node_modules/downshift/node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "node_modules/dset": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz",
@@ -10615,6 +10646,11 @@
       "integrity": "sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==",
       "dev": true
     },
+    "compute-scroll-into-view": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
+      "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
+    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -10911,6 +10947,30 @@
         "domhandler": "^5.0.1"
       }
     },
+    "downshift": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz",
+      "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==",
+      "requires": {
+        "@babel/runtime": "^7.14.8",
+        "compute-scroll-into-view": "^2.0.4",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2",
+        "tslib": "^2.3.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "17.0.2",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+          "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+        },
+        "tslib": {
+          "version": "2.5.3",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+          "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+        }
+      }
+    },
     "dset": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz",
diff --git a/extension/package.json b/extension/package.json
index 8ee8cb4c..8ccb4b13 100644
--- a/extension/package.json
+++ b/extension/package.json
@@ -14,7 +14,7 @@
   "displayName": "Continue",
   "pricing": "Free",
   "description": "Refine code 10x faster",
-  "version": "0.0.34",
+  "version": "0.0.35",
   "publisher": "Continue",
   "engines": {
     "vscode": "^1.74.0"
@@ -134,6 +134,7 @@
     "@styled-icons/heroicons-outline": "^10.47.0",
     "@vitejs/plugin-react-swc": "^3.3.2",
     "axios": "^1.2.5",
+    "downshift": "^7.6.0",
     "highlight.js": "^11.7.0",
     "posthog-js": "^1.63.3",
     "react-markdown": "^8.0.7",
diff --git a/extension/react-app/package-lock.json b/extension/react-app/package-lock.json
index dbcbc5cc..64440da6 100644
--- a/extension/react-app/package-lock.json
+++ b/extension/react-app/package-lock.json
@@ -10,6 +10,7 @@
       "dependencies": {
         "@styled-icons/heroicons-outline": "^10.47.0",
         "@types/vscode-webview": "^1.57.1",
+        "downshift": "^7.6.0",
         "posthog-js": "^1.58.0",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
@@ -1288,6 +1289,11 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/compute-scroll-into-view": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
+      "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
+    },
     "node_modules/css-color-keywords": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
@@ -1405,6 +1411,26 @@
       "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
       "dev": true
     },
+    "node_modules/downshift": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz",
+      "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==",
+      "dependencies": {
+        "@babel/runtime": "^7.14.8",
+        "compute-scroll-into-view": "^2.0.4",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "react": ">=16.12.0"
+      }
+    },
+    "node_modules/downshift/node_modules/react-is": {
+      "version": "17.0.2",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.311",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz",
@@ -3003,6 +3029,11 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "node_modules/typescript": {
       "version": "4.9.5",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
@@ -4050,6 +4081,11 @@
       "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
       "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="
     },
+    "compute-scroll-into-view": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz",
+      "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g=="
+    },
     "css-color-keywords": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
@@ -4131,6 +4167,25 @@
       "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
       "dev": true
     },
+    "downshift": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.0.tgz",
+      "integrity": "sha512-VSoTVynTAsabou/hbZ6HJHUVhtBiVOjQoBsCPcQq5eAROIGP+9XKMp9asAKQ3cEcUP4oe0fFdD2pziUjhFY33Q==",
+      "requires": {
+        "@babel/runtime": "^7.14.8",
+        "compute-scroll-into-view": "^2.0.4",
+        "prop-types": "^15.7.2",
+        "react-is": "^17.0.2",
+        "tslib": "^2.3.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "17.0.2",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+          "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
+        }
+      }
+    },
     "electron-to-chromium": {
       "version": "1.4.311",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.311.tgz",
@@ -5138,6 +5193,11 @@
       "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
       "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g=="
     },
+    "tslib": {
+      "version": "2.5.3",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz",
+      "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w=="
+    },
     "typescript": {
       "version": "4.9.5",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
diff --git a/extension/react-app/package.json b/extension/react-app/package.json
index 7d1211de..a53fbec8 100644
--- a/extension/react-app/package.json
+++ b/extension/react-app/package.json
@@ -11,6 +11,7 @@
   "dependencies": {
     "@styled-icons/heroicons-outline": "^10.47.0",
     "@types/vscode-webview": "^1.57.1",
+    "downshift": "^7.6.0",
     "posthog-js": "^1.58.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
diff --git a/extension/react-app/src/components/ComboBox.tsx b/extension/react-app/src/components/ComboBox.tsx
new file mode 100644
index 00000000..1b7c60e6
--- /dev/null
+++ b/extension/react-app/src/components/ComboBox.tsx
@@ -0,0 +1,146 @@
+import React, { useCallback } from "react";
+import { useCombobox } from "downshift";
+import styled from "styled-components";
+import {
+  buttonColor,
+  defaultBorderRadius,
+  secondaryDark,
+  vscBackground,
+} from ".";
+
+const mainInputFontSize = 16;
+const MainTextInput = styled.input`
+  padding: 8px;
+  font-size: ${mainInputFontSize}px;
+  border-radius: ${defaultBorderRadius};
+  border: 1px solid #ccc;
+  margin: 8px auto;
+  width: 100%;
+  background-color: ${vscBackground};
+  color: white;
+  outline: 1px solid orange;
+`;
+
+const UlMaxHeight = 200;
+const Ul = styled.ul<{
+  hidden: boolean;
+  showAbove: boolean;
+  ulHeightPixels: number;
+}>`
+  ${(props) =>
+    props.showAbove
+      ? `transform: translateY(-${props.ulHeightPixels + 8}px);`
+      : `transform: translateY(${2 * mainInputFontSize}px);`}
+  position: absolute;
+  background: ${vscBackground};
+  background-color: ${secondaryDark};
+  color: white;
+  font-family: "Fira Code", monospace;
+  max-height: ${UlMaxHeight}px;
+  overflow: scroll;
+  padding: 0;
+  ${({ hidden }) => hidden && "display: none;"}
+  border-radius: ${defaultBorderRadius};
+  overflow: hidden;
+  border: 0.5px solid gray;
+`;
+
+const Li = styled.li<{
+  highlighted: boolean;
+  selected: boolean;
+  isLastItem: boolean;
+}>`
+  ${({ highlighted }) => highlighted && "background: #aa0000;"}
+  ${({ selected }) => selected && "font-weight: bold;"}
+    padding: 0.5rem 0.75rem;
+  display: flex;
+  flex-direction: column;
+  ${({ isLastItem }) => isLastItem && "border-bottom: 1px solid gray;"}
+  border-top: 1px solid gray;
+  cursor: pointer;
+`;
+
+interface ComboBoxProps {
+  items: { name: string; description: string }[];
+  onInputValueChange: (inputValue: string) => void;
+  disabled?: boolean;
+  onEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
+}
+
+const ComboBox = React.forwardRef((props: ComboBoxProps, ref) => {
+  const [items, setItems] = React.useState(props.items);
+  const {
+    isOpen,
+    getToggleButtonProps,
+    getLabelProps,
+    getMenuProps,
+    getInputProps,
+    highlightedIndex,
+    getItemProps,
+    selectedItem,
+    setInputValue,
+  } = useCombobox({
+    onInputValueChange({ inputValue }) {
+      if (!inputValue) return;
+      props.onInputValueChange(inputValue);
+      setItems(
+        props.items.filter((item) =>
+          item.name.toLowerCase().startsWith(inputValue.toLowerCase())
+        )
+      );
+    },
+    items,
+    itemToString(item) {
+      return item ? item.name : "";
+    },
+  });
+
+  const divRef = React.useRef<HTMLDivElement>(null);
+  const ulRef = React.useRef<HTMLUListElement>(null);
+  const showAbove = () => {
+    return (divRef.current?.getBoundingClientRect().top || 0) > UlMaxHeight;
+  };
+
+  return (
+    <div className="flex px-2" ref={divRef} hidden={!isOpen}>
+      <MainTextInput
+        disabled={props.disabled}
+        placeholder="Ask anything:"
+        {...getInputProps({
+          onKeyDown: (event) => {
+            if (event.key === "Enter" && (!isOpen || items.length === 0)) {
+              // Prevent Downshift's default 'Enter' behavior.
+              (event.nativeEvent as any).preventDownshiftDefault = true;
+              if (props.onEnter) props.onEnter(event);
+              setInputValue("");
+            }
+          },
+          ref: ref as any,
+        })}
+      />
+      <Ul
+        {...getMenuProps({
+          ref: ulRef,
+        })}
+        showAbove={showAbove()}
+        ulHeightPixels={ulRef.current?.getBoundingClientRect().height || 0}
+      >
+        {isOpen &&
+          items.map((item, index) => (
+            <Li
+              key={`${item.name}${index}`}
+              {...getItemProps({ item, index })}
+              highlighted={highlightedIndex === index}
+              selected={selectedItem === item}
+            >
+              <span>
+                {item.name}: {item.description}
+              </span>
+            </Li>
+          ))}
+      </Ul>
+    </div>
+  );
+});
+
+export default ComboBox;
diff --git a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
index 71303c70..824bb086 100644
--- a/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
+++ b/extension/react-app/src/hooks/ContinueGUIClientProtocol.ts
@@ -9,6 +9,10 @@ abstract class AbstractContinueGUIClientProtocol {
 
   abstract onStateUpdate(state: any): void;
 
+  abstract onAvailableSlashCommands(
+    callback: (commands: { name: string; description: string }[]) => void
+  ): void;
+
   abstract sendClear(): void;
 
   abstract retryAtIndex(index: number): void;
diff --git a/extension/react-app/src/hooks/useContinueGUIProtocol.ts b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
index a8e28fc5..59397742 100644
--- a/extension/react-app/src/hooks/useContinueGUIProtocol.ts
+++ b/extension/react-app/src/hooks/useContinueGUIProtocol.ts
@@ -45,6 +45,16 @@ class ContinueGUIClientProtocol extends AbstractContinueGUIClientProtocol {
     });
   }
 
+  onAvailableSlashCommands(
+    callback: (commands: { name: string; description: string }[]) => void
+  ) {
+    this.messenger.onMessageType("available_slash_commands", (data: any) => {
+      if (data.commands) {
+        callback(data.commands);
+      }
+    });
+  }
+
   sendClear() {
     this.messenger.send("clear_history", {});
   }
diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index cb7a5440..1569c178 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -12,7 +12,8 @@ import { History } from "../../../schema/History";
 import { HistoryNode } from "../../../schema/HistoryNode";
 import StepContainer from "../components/StepContainer";
 import useContinueGUIProtocol from "../hooks/useWebsocket";
-import { Trash } from "@styled-icons/heroicons-outline";
+import { BookOpen, Trash } from "@styled-icons/heroicons-outline";
+import ComboBox from "../components/ComboBox";
 let TopGUIDiv = styled.div`
   display: grid;
   grid-template-columns: 1fr;
@@ -42,128 +43,132 @@ interface GUIProps {
 function GUI(props: GUIProps) {
   const [waitingForSteps, setWaitingForSteps] = useState(false);
   const [userInputQueue, setUserInputQueue] = useState<string[]>([]);
-  const [history, setHistory] = useState<History | undefined>({
-    timeline: [
-      {
-        step: {
-          name: "Waiting for user input",
-          cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-          description:
-            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
-        },
-        observation: {
-          title: "ERROR FOUND",
-          error:
-            "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
-        },
-        output: [
-          {
-            traceback: {
-              frames: [
-                {
-                  filepath:
-                    "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-                  lineno: 7,
-                  function: "<module>",
-                  code: "print(sum(first, second))",
-                },
-              ],
-              message: "unsupported operand type(s) for +: 'int' and 'str'",
-              error_type:
-                '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
-              full_traceback:
-                "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
-            },
-          },
-          null,
-        ],
-      },
-      {
-        step: {
-          name: "EditCodeStep",
-          range_in_files: [
-            {
-              filepath:
-                "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-              range: {
-                start: {
-                  line: 0,
-                  character: 0,
-                },
-                end: {
-                  line: 6,
-                  character: 25,
-                },
-              },
-            },
-          ],
-          prompt:
-            "I ran into this problem with my Python code:\n\n                Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n                Below are the files that might need to be fixed:\n\n                {code}\n\n                This is what the code should be in order to avoid the problem:\n",
-          description:
-            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
-        },
-        output: [
-          null,
-          {
-            reversible: true,
-            actions: [
-              {
-                reversible: true,
-                filesystem: {},
-                filepath:
-                  "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-                range: {
-                  start: {
-                    line: 0,
-                    character: 0,
-                  },
-                  end: {
-                    line: 6,
-                    character: 25,
-                  },
-                },
-                replacement:
-                  "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))",
-              },
-            ],
-          },
-        ],
-      },
-      {
-        step: {
-          name: "SolveTracebackStep",
-          traceback: {
-            frames: [
-              {
-                filepath:
-                  "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-                lineno: 7,
-                function: "<module>",
-                code: "print(sum(first, second))",
-              },
-            ],
-            message: "unsupported operand type(s) for +: 'int' and 'str'",
-            error_type:
-              '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
-            full_traceback:
-              "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
-          },
-          description: "Running step: SolveTracebackStep",
-        },
-        output: [null, null],
-      },
-      {
-        step: {
-          name: "RunCodeStep",
-          cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
-          description:
-            "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`",
-        },
-        output: [null, null],
-      },
-    ],
-    current_index: 3,
-  } as any);
+  const [availableSlashCommands, setAvailableSlashCommands] = useState<
+    { name: string; description: string }[]
+  >([]);
+  const [history, setHistory] = useState<History | undefined>();
+  // {
+  //   timeline: [
+  //     {
+  //       step: {
+  //         name: "Waiting for user input",
+  //         cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+  //         description:
+  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and ```\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
+  //       },
+  //       observation: {
+  //         title: "ERROR FOUND",
+  //         error:
+  //           "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
+  //       },
+  //       output: [
+  //         {
+  //           traceback: {
+  //             frames: [
+  //               {
+  //                 filepath:
+  //                   "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+  //                 lineno: 7,
+  //                 function: "<module>",
+  //                 code: "print(sum(first, second))",
+  //               },
+  //             ],
+  //             message: "unsupported operand type(s) for +: 'int' and 'str'",
+  //             error_type:
+  //               '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
+  //             full_traceback:
+  //               "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
+  //           },
+  //         },
+  //         null,
+  //       ],
+  //     },
+  //     {
+  //       step: {
+  //         name: "EditCodeStep",
+  //         range_in_files: [
+  //           {
+  //             filepath:
+  //               "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+  //             range: {
+  //               start: {
+  //                 line: 0,
+  //                 character: 0,
+  //               },
+  //               end: {
+  //                 line: 6,
+  //                 character: 25,
+  //               },
+  //             },
+  //           },
+  //         ],
+  //         prompt:
+  //           "I ran into this problem with my Python code:\n\n                Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'\n\n                Below are the files that might need to be fixed:\n\n                {code}\n\n                This is what the code should be in order to avoid the problem:\n",
+  //         description:
+  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py` and\n```python\nprint(sum(first, second))\n```\n- Testing\n- Testing 2\n- Testing 3",
+  //       },
+  //       output: [
+  //         null,
+  //         {
+  //           reversible: true,
+  //           actions: [
+  //             {
+  //               reversible: true,
+  //               filesystem: {},
+  //               filepath:
+  //                 "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+  //               range: {
+  //                 start: {
+  //                   line: 0,
+  //                   character: 0,
+  //                 },
+  //                 end: {
+  //                   line: 6,
+  //                   character: 25,
+  //                 },
+  //               },
+  //               replacement:
+  //                 "\nfrom sum import sum\n\nfirst = 1\nsecond = 2\n\nprint(sum(first, second))",
+  //             },
+  //           ],
+  //         },
+  //       ],
+  //     },
+  //     {
+  //       step: {
+  //         name: "SolveTracebackStep",
+  //         traceback: {
+  //           frames: [
+  //             {
+  //               filepath:
+  //                 "/Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+  //               lineno: 7,
+  //               function: "<module>",
+  //               code: "print(sum(first, second))",
+  //             },
+  //           ],
+  //           message: "unsupported operand type(s) for +: 'int' and 'str'",
+  //           error_type:
+  //             '          ^^^^^^^^^^^^^^^^^^\n  File "/Users/natesesti/Desktop/continue/extension/examples/python/sum.py", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError',
+  //           full_traceback:
+  //             "Traceback (most recent call last):\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/main.py\", line 7, in <module>\n    print(sum(first, second))\n          ^^^^^^^^^^^^^^^^^^\n  File \"/Users/natesesti/Desktop/continue/extension/examples/python/sum.py\", line 2, in sum\n    return a + b\n           ~~^~~\nTypeError: unsupported operand type(s) for +: 'int' and 'str'",
+  //         },
+  //         description: "Running step: SolveTracebackStep",
+  //       },
+  //       output: [null, null],
+  //     },
+  //     {
+  //       step: {
+  //         name: "RunCodeStep",
+  //         cmd: "python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py",
+  //         description:
+  //           "Run `python3 /Users/natesesti/Desktop/continue/extension/examples/python/main.py`",
+  //       },
+  //       output: [null, null],
+  //     },
+  //   ],
+  //   current_index: 3,
+  // } as any);
 
   const topGuiDivRef = useRef<HTMLDivElement>(null);
   const client = useContinueGUIProtocol();
@@ -197,13 +202,24 @@ function GUI(props: GUIProps) {
 
       scrollToBottom();
     });
+    client?.onAvailableSlashCommands((commands) => {
+      console.log("Received available slash commands: ", commands);
+      setAvailableSlashCommands(
+        commands.map((c) => {
+          return {
+            name: "/" + c.name,
+            description: c.description,
+          };
+        })
+      );
+    });
   }, [client]);
 
   useEffect(() => {
     scrollToBottom();
   }, [waitingForSteps]);
 
-  const mainTextInputRef = useRef<HTMLTextAreaElement>(null);
+  const mainTextInputRef = useRef<HTMLInputElement>(null);
 
   useEffect(() => {
     if (mainTextInputRef.current) {
@@ -246,8 +262,6 @@ function GUI(props: GUIProps) {
           return [...queue, input];
         });
       }
-      mainTextInputRef.current.value = "";
-      mainTextInputRef.current.style.height = "";
     }
 
     setWaitingForSteps(true);
@@ -270,7 +284,12 @@ function GUI(props: GUIProps) {
       }}
     >
       <TopBar>
-        <h3>Continue</h3>
+        <a href="https://continue.dev/docs" className="no-underline">
+          <HeaderButton style={{ padding: "3px" }}>
+            Continue Docs
+            <BookOpen size="1.6em" />
+          </HeaderButton>
+        </a>
         <HeaderButton style={{ padding: "3px" }}>
           Clear History
           <Trash
@@ -323,7 +342,7 @@ function GUI(props: GUIProps) {
         })}
       </div>
 
-      <MainTextInput
+      <ComboBox
         disabled={
           history
             ? history.timeline[history.current_index].step.name ===
@@ -331,22 +350,13 @@ function GUI(props: GUIProps) {
             : false
         }
         ref={mainTextInputRef}
-        onKeyDown={(e) => {
-          if (e.key === "Enter") {
-            onMainTextInput();
-            e.stopPropagation();
-            e.preventDefault();
-          }
-        }}
-        rows={1}
-        onChange={() => {
-          const textarea = mainTextInputRef.current!;
-          textarea.style.height = ""; /* Reset the height*/
-          textarea.style.height = `${Math.min(
-            textarea.scrollHeight - 15,
-            500
-          )}px`;
+        onEnter={(e) => {
+          onMainTextInput();
+          e.stopPropagation();
+          e.preventDefault();
         }}
+        onInputValueChange={() => {}}
+        items={availableSlashCommands}
       />
       <ContinueButton onClick={onMainTextInput} />
     </TopGUIDiv>
diff --git a/extension/scripts/continuedev-0.1.1-py3-none-any.whl b/extension/scripts/continuedev-0.1.1-py3-none-any.whl
index 42f3d4a3..3d7639a9 100644
Binary files a/extension/scripts/continuedev-0.1.1-py3-none-any.whl and b/extension/scripts/continuedev-0.1.1-py3-none-any.whl differ
-- 
cgit v1.2.3-70-g09d2


From 01cfbc179a33c99d55acdc989dbafd554db16a92 Mon Sep 17 00:00:00 2001
From: Nate Sesti <sestinj@gmail.com>
Date: Mon, 12 Jun 2023 21:34:05 -0700
Subject: clear history button fix

---
 extension/react-app/src/tabs/gui.tsx | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/extension/react-app/src/tabs/gui.tsx b/extension/react-app/src/tabs/gui.tsx
index 1569c178..a3a48410 100644
--- a/extension/react-app/src/tabs/gui.tsx
+++ b/extension/react-app/src/tabs/gui.tsx
@@ -290,14 +290,14 @@ function GUI(props: GUIProps) {
             <BookOpen size="1.6em" />
           </HeaderButton>
         </a>
-        <HeaderButton style={{ padding: "3px" }}>
+        <HeaderButton
+          onClick={() => {
+            client?.sendClear();
+          }}
+          style={{ padding: "3px" }}
+        >
           Clear History
-          <Trash
-            size="1.6em"
-            onClick={() => {
-              client?.sendClear();
-            }}
-          />
+          <Trash size="1.6em" />
         </HeaderButton>
       </TopBar>
 
-- 
cgit v1.2.3-70-g09d2