diff --git a/src/components/FileBrowser.vue b/src/components/FileBrowser.vue index 1e5b853..44c1502 100644 --- a/src/components/FileBrowser.vue +++ b/src/components/FileBrowser.vue @@ -745,7 +745,8 @@ const editorExtensions = [yaml(), oneDark]; const fileModified = computed(() => fileContent.value !== originalContent.value); const editorAssistContext = computed(() => ({ - scope: "system", + scope: props.deploymentName ? "deployment" : "system", + deployment: props.deploymentName || undefined, subject: editingFile.value?.path || editingFile.value?.name || "file", seedContext: fileContent.value, })); @@ -1165,6 +1166,8 @@ const viewFile = async (file: FileInfo) => { loadingFileContent.value = true; fileContent.value = ""; originalContent.value = ""; + // Writes from an earlier conversation are already in the file being loaded. + seenAssistWrites.value = assistWritesToOpenFile(); try { const response = await fileApi.value.getContent(file.path); @@ -1186,6 +1189,8 @@ const openFileEditor = async (file: FileInfo) => { loadingFileContent.value = true; fileContent.value = ""; originalContent.value = ""; + // Writes from an earlier conversation are already in the file being loaded. + seenAssistWrites.value = assistWritesToOpenFile(); try { const response = await fileApi.value.getContent(file.path); @@ -1232,6 +1237,63 @@ const saveFile = async () => { } }; +// A file write by the assistant leaves the open editor buffer stale, and a +// later manual save would clobber it. Track completed assistant writes to the +// open file and reload the buffer, unless the operator has unsaved edits. +const seenAssistWrites = ref(0); + +const assistWritesToOpenFile = (): number => { + const session = assistStore.session; + const open = (editingFile.value?.path || "").replace(/^\/+/, ""); + if (!session || !open) return 0; + let count = 0; + for (const turn of session.messages) { + for (const step of turn.tool_steps || []) { + if (step.name !== "write_deployment_file" || !step.result || step.result.startsWith("Error")) continue; + try { + const target = String(JSON.parse(step.arguments || "{}").path || "").replace(/^\/+/, ""); + if (target && (target === open || open.endsWith("/" + target))) count++; + } catch { + // Unparseable arguments cannot name the open file. + } + } + } + return count; +}; + +const reloadEditorContent = async () => { + if (!editingFile.value) return; + try { + const response = await fileApi.value.getContent(editingFile.value.path); + fileContent.value = response.data; + originalContent.value = response.data; + notifications.info("File updated", `${editingFile.value.name} was reloaded with the assistant's change`); + } catch { + // Keep the buffer; reopening the file re-reads it. + } +}; + +watch( + () => assistStore.session, + async () => { + if (!showEditorModal.value || !editingFile.value) return; + const count = assistWritesToOpenFile(); + if (count <= seenAssistWrites.value) { + seenAssistWrites.value = count; + return; + } + seenAssistWrites.value = count; + if (!viewOnly.value && fileModified.value) { + notifications.warning( + "File changed on disk", + "The assistant updated this file, but you have unsaved edits. Saving now would overwrite its change.", + ); + return; + } + await reloadEditorContent(); + }, +); + watch(currentPath, () => { fetchFiles(); }); diff --git a/src/views/DeploymentDetailView.vue b/src/views/DeploymentDetailView.vue index 8b93f01..448e6be 100755 --- a/src/views/DeploymentDetailView.vue +++ b/src/views/DeploymentDetailView.vue @@ -604,9 +604,12 @@

Environment Variables

- +
+ + +
@@ -1825,6 +1828,7 @@ import { matchTypeHints, describeBlockedRule } from "@/utils/protectedMode"; import { usePlanFlow } from "@/composables/usePlanFlow"; import SplitActionButton from "@/components/base/SplitActionButton.vue"; import SubTabs from "@/components/base/SubTabs.vue"; +import AssistButton from "@/components/ai/AssistButton.vue"; import InlineAssist from "@/components/ai/InlineAssist.vue"; import { useAssistStore } from "@/stores/assist"; import Icon from "@/components/base/Icon.vue"; @@ -2817,6 +2821,17 @@ const serviceConfigAssistContext = computed(() => ({ seedContext: serviceConfig.value, })); +// Keys only: a value typed here may not be saved yet, so the server-side +// redactor cannot know it. Withholding values keeps drafts off the model. +const envAssistContext = computed(() => ({ + scope: "deployment", + deployment: route.params.name as string, + subject: "environment variables", + seedContext: + "Environment variable keys configured for this deployment (values withheld):\n" + + (envVars.value.length ? envVars.value.map((env) => "- " + env.key).join("\n") : "(none)"), +})); + const operationAssistContext = computed(() => ({ scope: "deployment", deployment: route.params.name as string, @@ -4047,6 +4062,12 @@ onUnmounted(() => { margin-bottom: var(--space-4); } +.env-header-actions { + display: flex; + gap: var(--space-2); + align-items: center; +} + .env-header h3 { font-size: var(--text-lg); font-weight: var(--font-semibold);