@demicodes/web-ui 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@demicodes/web-ui",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
@@ -24,9 +24,9 @@
24
24
  "typecheck": "vue-tsc --noEmit -p tsconfig.json"
25
25
  },
26
26
  "dependencies": {
27
- "@demicodes/agent": "^0.3.2",
27
+ "@demicodes/agent": "^0.4.0",
28
28
  "@demicodes/core": "^0.3.2",
29
- "@demicodes/utils": "^0.3.2",
29
+ "@demicodes/utils": "^0.4.0",
30
30
  "@floating-ui/vue": "^2.0.0",
31
31
  "@mingcute/vue": "^1.4.1",
32
32
  "@tanstack/vue-virtual": "^3.13.29",
@@ -109,6 +109,7 @@ watch(
109
109
  :conversation-id="props.conversationId"
110
110
  :is-thinking-streaming="isStreamingThinkingAt(item.index)"
111
111
  :thinking-ended-at="thinkingEndedAt(item.index)"
112
+ :recoverable="item.index === renderBlocks.length - 1 && props.phase === 'idle'"
112
113
  @continue="emit('continue')"
113
114
  @retry="emit('retry')"
114
115
  @delete-pending-steer="(id) => emit('deletePendingSteer', id)"
@@ -2,6 +2,11 @@
2
2
  import { ArrowRightLine } from '@mingcute/vue/arrow-right'
3
3
  import { t } from '@demicodes/web-ui/infra/i18n'
4
4
 
5
+ const props = defineProps<{
6
+ /** Offer the Continue action; see ErrorBlock — only the tail block recovers. */
7
+ recoverable: boolean
8
+ }>()
9
+
5
10
  const emit = defineEmits<{
6
11
  continue: []
7
12
  retry: []
@@ -16,6 +21,7 @@ function handleRecovery() {
16
21
  <div class="flex h-6 items-center justify-between">
17
22
  <span class="rounded bg-fg-ghost/60 px-1.5 py-0.5 text-[11px] text-fg-subtle">{{ t('agent.block.aborted') }}</span>
18
23
  <div
24
+ v-if="props.recoverable"
19
25
  class="flex cursor-pointer items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-fg-subtle transition-colors hover:bg-active hover:text-fg-body"
20
26
  @click="handleRecovery"
21
27
  >
@@ -16,6 +16,10 @@ const props = defineProps<{
16
16
  conversationId: string
17
17
  isThinkingStreaming: boolean
18
18
  thinkingEndedAt?: string | null
19
+ /** Whether this block may offer recovery actions (Continue) — true only for
20
+ * the conversation's tail block while the session is idle, so stale error
21
+ * blocks never present a second, competing recovery entry point. */
22
+ recoverable: boolean
19
23
  }>()
20
24
 
21
25
  const emit = defineEmits<{
@@ -67,12 +71,14 @@ const attrs = useAttrs()
67
71
  <ErrorBlock
68
72
  :message="block.message"
69
73
  :code="block.code"
74
+ :recoverable="props.recoverable"
70
75
  @continue="emit('continue')"
71
76
  @retry="emit('retry')"
72
77
  />
73
78
  </div>
74
79
  <div v-else-if="block.type === 'abort'" class="px-[var(--agent-pad-x,2rem)]">
75
80
  <AbortedBlock
81
+ :recoverable="props.recoverable"
76
82
  @continue="emit('continue')"
77
83
  @retry="emit('retry')"
78
84
  />
@@ -11,6 +11,9 @@ const props = defineProps<{
11
11
  message: string
12
12
  code?: string | null
13
13
  stack?: string
14
+ /** Offer the Continue action. Only the conversation's tail error is
15
+ * recoverable — older errors are records, not entry points. */
16
+ recoverable: boolean
14
17
  }>()
15
18
 
16
19
  const emit = defineEmits<{
@@ -66,7 +69,7 @@ const { copy, copied } = useClipboard({ copiedDuring: 1500 })
66
69
  <pre class="whitespace-pre-wrap break-words font-mono text-[11px] leading-4.5 text-on-danger-muted">{{ props.stack }}</pre>
67
70
  </div>
68
71
  </div>
69
- <div class="flex items-center justify-end border-t border-on-danger-muted px-3 py-2">
72
+ <div v-if="props.recoverable" class="flex items-center justify-end border-t border-on-danger-muted px-3 py-2">
70
73
  <div
71
74
  class="flex cursor-pointer items-center gap-1.5 rounded-md bg-tint-danger-strong px-2.5 py-1 text-[12px] font-medium text-on-danger transition-colors hover:bg-tint-danger-strong"
72
75
  @click="handleRecovery"
@@ -129,7 +129,18 @@ export class ConversationRuntime {
129
129
  }
130
130
 
131
131
  async resume(): Promise<void> {
132
- await (await this.ensureOpen()).resume()
132
+ const client = await this.ensureOpen()
133
+ // Optimistic: the server only reports `running` after a round-trip, which
134
+ // reads as a dead Continue button in the meantime. Flip immediately; the
135
+ // next phase_changed event carries the real phase either way, and a failed
136
+ // resume rolls back so the recovery action reappears.
137
+ this.state.phase = 'running'
138
+ try {
139
+ await client.resume()
140
+ } catch (error) {
141
+ this.state.phase = 'idle'
142
+ throw error
143
+ }
133
144
  }
134
145
 
135
146
  async compact(): Promise<void> {