diff --git a/web/src/api/client.ts b/web/src/api/client.ts
index edb0e1d..a7c1503 100644
--- a/web/src/api/client.ts
+++ b/web/src/api/client.ts
@@ -34,11 +34,13 @@ export async function request(path: string, opts: RequestOptions = {}): Promi
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS)
+ let onAbort: (() => void) | undefined
if (opts.signal) {
if (opts.signal.aborted) {
controller.abort()
} else {
- opts.signal.addEventListener('abort', () => controller.abort(), { once: true })
+ onAbort = () => controller.abort()
+ opts.signal.addEventListener('abort', onAbort, { once: true })
}
}
@@ -78,6 +80,9 @@ export async function request(path: string, opts: RequestOptions = {}): Promi
return data as T
} finally {
clearTimeout(timer)
+ if (opts.signal && onAbort) {
+ opts.signal.removeEventListener('abort', onAbort)
+ }
}
}
diff --git a/web/src/components/acp/AgentKindForm.vue b/web/src/components/acp/AgentKindForm.vue
index 80e64ca..c8fcae1 100644
--- a/web/src/components/acp/AgentKindForm.vue
+++ b/web/src/components/acp/AgentKindForm.vue
@@ -57,11 +57,12 @@ const allowlistText = computed({
})
interface EnvRow {
+ id: string
k: string
v: string
}
const envEntries = ref(
- Object.entries(local.value.env).map(([k, v]) => ({ k, v })),
+ Object.entries(local.value.env).map(([k, v]) => ({ id: crypto.randomUUID(), k, v })),
)
watch(
@@ -77,7 +78,7 @@ watch(
)
function addEnv() {
- envEntries.value.push({ k: '', v: '' })
+ envEntries.value.push({ id: crypto.randomUUID(), k: '', v: '' })
}
function removeEnv(idx: number) {
envEntries.value.splice(idx, 1)
@@ -94,6 +95,7 @@ const mcpTypeOptions = [
]
interface McpRow {
+ id: string
name: string
type: 'stdio' | 'http' | 'sse'
command: string
@@ -123,6 +125,7 @@ function parseKV(s: string): Record | undefined {
function specsToRows(specs: McpServerSpec[]): McpRow[] {
return specs.map((s) => ({
+ id: crypto.randomUUID(),
name: s.name,
type: s.type,
command: s.command ?? '',
@@ -173,6 +176,7 @@ watch(
function addMcpServer() {
mcpRows.value.push({
+ id: crypto.randomUUID(),
name: '',
type: 'http',
command: '',
@@ -258,7 +262,7 @@ function removeMcpServer(idx: number) {
diff --git a/web/src/utils/markdown.ts b/web/src/utils/markdown.ts
new file mode 100644
index 0000000..b90c77b
--- /dev/null
+++ b/web/src/utils/markdown.ts
@@ -0,0 +1,3 @@
+import MarkdownIt from 'markdown-it'
+
+export const md = new MarkdownIt({ html: false, breaks: true, linkify: true })
diff --git a/web/src/views/chat/ChatDetailView.vue b/web/src/views/chat/ChatDetailView.vue
index b3e57ce..6995e8d 100644
--- a/web/src/views/chat/ChatDetailView.vue
+++ b/web/src/views/chat/ChatDetailView.vue
@@ -94,6 +94,7 @@
diff --git a/web/src/views/chat/components/MessageBubble.vue b/web/src/views/chat/components/MessageBubble.vue
index f294561..f4a4c10 100644
--- a/web/src/views/chat/components/MessageBubble.vue
+++ b/web/src/views/chat/components/MessageBubble.vue
@@ -93,13 +93,10 @@