@7n/tauri-components 0.13.11 → 0.15.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.15.0] - 2026-07-21
4
+
5
+ ### Changed
6
+
7
+ - Storybook доведено до канону (rules-lang-js 0.12.0): динамічний viteFinal (VueMacros-стійкий, `empty-vite.config.js`-обхід), msw-storybook-addon + mswLoader, canonical `scripts.storybook`, devDeps governance (msw@2.11.3/msw-storybook-addon@2.0.5 пін у npm/, build-тулінг у корені), CI (setup-playwright-chromium + lint-storybook.yml). Кастомний Material Symbols iconSet/iconMapFn (sym_o_/r_/s_) збережено — легітимна локальна особливість пакета.
8
+
9
+ ## [0.14.0] - 2026-07-21
10
+
11
+ ### Added
12
+
13
+ - AgentDialog: інкрементальний стрімінг відповіді (текст/tool-call з'являються по мірі надходження session/update-чанків, а не лише після завершення всього ходу). AuditDialog: коректно показує нативні ACP permission-request поряд з MCP tool-call approval в одному списку.
14
+
3
15
  ## [0.13.11] - 2026-07-21
4
16
 
5
17
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/tauri-components",
3
- "version": "0.13.11",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "description": "Shared LLM agent engine + Vue/Quasar UI for Tauri apps (chat, journal, trust-tier approval).",
6
6
  "license": "MIT",
@@ -73,14 +73,18 @@
73
73
  "bun": ">=1.3",
74
74
  "node": ">=24"
75
75
  },
76
+ "scripts": {
77
+ "storybook": "storybook dev -p 6006 --no-open"
78
+ },
76
79
  "devDependencies": {
77
- "@quasar/extras": "^2.0.2",
78
- "@storybook/addon-vitest": "^10.5.3",
79
80
  "@storybook/vue3-vite": "^10.5.3",
80
- "@vitejs/plugin-vue": "^6.0.8",
81
- "@vitest/browser": "^4.1.10",
82
- "@vitest/browser-playwright": "^4.1.10",
83
- "playwright": "^1.61.1",
81
+ "msw": "2.11.3",
82
+ "msw-storybook-addon": "2.0.5",
84
83
  "storybook": "^10.5.3"
84
+ },
85
+ "msw": {
86
+ "workerDirectory": [
87
+ ".storybook/public"
88
+ ]
85
89
  }
86
90
  }
@@ -142,12 +142,14 @@ async function scrollToEnd() {
142
142
  }
143
143
 
144
144
  /**
145
- * Append an agent turn, latch the conversation id, refresh the graph on writes.
145
+ * Fill in the live turn with the final result, latch the conversation id,
146
+ * refresh the graph on writes.
146
147
  * @param {object} outcome structured result from request/respond
148
+ * @param {object} turn the live turn pushed at the start of send(), updated in place
147
149
  */
148
- function apply(outcome) {
150
+ function apply(outcome, turn) {
149
151
  if (outcome.requestId) requestId.value = outcome.requestId
150
- turns.value.push({ role: 'agent', result: outcome, agentLabel: activeAgentLabel.value })
152
+ turn.result = outcome
151
153
  if (outcome.actions?.some(action => action.envelope?.ok)) emit('ran')
152
154
  scrollToEnd()
153
155
  }
@@ -202,17 +204,33 @@ async function send() {
202
204
  }
203
205
  prompt.value = ''
204
206
  turns.value.push({ role: 'user', text })
207
+ // Only a fresh request() actually spawns with the currently-selected
208
+ // agent/tier — a respond() keeps talking to whatever the conversation
209
+ // already started with, so the label must not move mid-conversation.
210
+ if (!requestId.value) {
211
+ activeAgentLabel.value = currentAgentLabel()
212
+ activeSpawnKey.value = currentSpawnKey()
213
+ }
214
+ // Pushed before the call resolves and updated in place as session/update
215
+ // chunks stream in (onChunk below), so the reply grows live instead of
216
+ // only appearing once the whole turn finishes.
217
+ const liveTurn = {
218
+ role: 'agent',
219
+ agentLabel: activeAgentLabel.value,
220
+ result: { status: 'running', summary: '', actions: [], question: null, pendingApproval: null }
221
+ }
222
+ turns.value.push(liveTurn)
205
223
  scrollToEnd()
206
224
  running.value = true
207
225
  try {
208
- // Only a fresh request() actually spawns with the currently-selected
209
- // agent/tier a respond() keeps talking to whatever the conversation
210
- // already started with, so the label must not move mid-conversation.
211
- if (!requestId.value) {
212
- activeAgentLabel.value = currentAgentLabel()
213
- activeSpawnKey.value = currentSpawnKey()
226
+ const onChunk = ({ text: liveText, actions }) => {
227
+ liveTurn.result = { ...liveTurn.result, summary: liveText, actions }
228
+ scrollToEnd()
214
229
  }
215
- apply(await (requestId.value ? respond(requestId.value, payload) : request(payload)))
230
+ const outcome = await (requestId.value
231
+ ? respond(requestId.value, payload, { onChunk })
232
+ : request(payload, { onChunk }))
233
+ apply(outcome, liveTurn)
216
234
  } catch (error) {
217
235
  $q.notify({ type: 'negative', message: String(error?.message ?? error) })
218
236
  } finally {
@@ -25,7 +25,11 @@
25
25
  <div v-if="expandedId === rec.id" class="audit-body">
26
26
  <RequestView @respond="msg => onRespond(rec, msg)" :result="rec" :busy="busyId === rec.id" />
27
27
  <div v-if="rec.status === 'needs_approval' && rec.pendingApproval" class="audit-approval">
28
- <div class="audit-pending">
28
+ <div v-if="rec.pendingApproval.kind === 'acp'" class="audit-pending">
29
+ Permission request:
30
+ <code>{{ rec.pendingApproval.toolCall?.title ?? rec.pendingApproval.toolCall?.toolCallId }}</code>
31
+ </div>
32
+ <div v-else class="audit-pending">
29
33
  Approve action: <code>{{ rec.pendingApproval.tool }}({{ JSON.stringify(rec.pendingApproval.input) }})</code>
30
34
  </div>
31
35
  <div class="row q-gutter-sm">
@@ -3,7 +3,7 @@ type: Vue Component
3
3
  title: AgentDialog.vue
4
4
  resource: npm/src/components/AgentDialog.vue
5
5
  docgen:
6
- crc: 9ff2a698
6
+ crc: 6be89ae5
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@ type: Vue Component
3
3
  title: AuditDialog.vue
4
4
  resource: npm/src/components/AuditDialog.vue
5
5
  docgen:
6
- crc: 8c440daf
6
+ crc: c716181c
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -101,7 +101,7 @@ function applySessionUpdate(state, update) {
101
101
  * @param {object} params turn parameters
102
102
  * @param {string} params.sessionKey handle from `createAcpSession`
103
103
  * @param {string} params.text prompt text for this turn
104
- * @param {(update: object) => void} [params.onChunk] optional live callback for each raw session/update (UI streaming)
104
+ * @param {(snapshot: {text: string, actions: {tool: string, input: object, envelope: object|null}[]}) => void} [params.onChunk] optional live callback with the turn's accumulated text/tool-calls so far, fired on every session/update (UI streaming)
105
105
  * @returns {Promise<{content: string, steps: number, trace: object[], messages: object[], stopped?: string}>} runAgent()-shaped result
106
106
  */
107
107
  export async function runAcpTurn({ sessionKey, text, onChunk }) {
@@ -110,7 +110,7 @@ export async function runAcpTurn({ sessionKey, text, onChunk }) {
110
110
  const unlisten = await listen('acp://session-update', event => {
111
111
  if (event.payload?.sessionKey !== sessionKey) return
112
112
  applySessionUpdate(state, event.payload.update)
113
- onChunk?.(event.payload.update)
113
+ onChunk?.({ text: state.text, actions: state.calls.values().toArray() })
114
114
  })
115
115
 
116
116
  try {
@@ -175,10 +175,10 @@ export function createAcpAgentKit({
175
175
  /**
176
176
  * Start a new ACP-backed request: spawns the agent session and runs the
177
177
  * first prompt turn.
178
- * @param {{intent: string, agent: object}} opts `agent` is passed straight to `createAcpSession` (agentKind/command/args/env/cwd/…)
178
+ * @param {{intent: string, agent: object, onChunk?: (snapshot: {text: string, actions: object[]}) => void}} opts `agent` is passed straight to `createAcpSession` (agentKind/command/args/env/cwd/…); `onChunk` streams live text/tool-calls as the turn runs
179
179
  * @returns {Promise<object>} structured result envelope
180
180
  */
181
- async request({ intent, agent }) {
181
+ async request({ intent, agent, onChunk }) {
182
182
  await ensureListening()
183
183
  const id = await journal.create({ intent, actor: AGENT_ACTOR })
184
184
  await journal.update(id, { status: 'running' })
@@ -191,15 +191,15 @@ export function createAcpAgentKit({
191
191
  activeSessionKey = session.sessionKey
192
192
  activeRequestId = id
193
193
  await journal.update(id, { acp: { agentKind: session.agentKind, sessionKey: session.sessionKey } })
194
- return runAndJournal(id, [], deps.runAcpTurn({ sessionKey: activeSessionKey, text: intent }))
194
+ return runAndJournal(id, [], deps.runAcpTurn({ sessionKey: activeSessionKey, text: intent, onChunk }))
195
195
  },
196
196
 
197
197
  /**
198
198
  * Continue the active session with a follow-up message.
199
- * @param {{requestId: string, message: string}} opts resume parameters
199
+ * @param {{requestId: string, message: string, onChunk?: (snapshot: {text: string, actions: object[]}) => void}} opts resume parameters; `onChunk` streams live text/tool-calls as the turn runs
200
200
  * @returns {Promise<object>} updated result envelope
201
201
  */
202
- async respond({ requestId, message }) {
202
+ async respond({ requestId, message, onChunk }) {
203
203
  if (!activeSessionKey) {
204
204
  return {
205
205
  requestId,
@@ -228,7 +228,7 @@ export function createAcpAgentKit({
228
228
  return runAndJournal(
229
229
  requestId,
230
230
  record.actions ?? [],
231
- deps.runAcpTurn({ sessionKey: activeSessionKey, text: message })
231
+ deps.runAcpTurn({ sessionKey: activeSessionKey, text: message, onChunk })
232
232
  )
233
233
  },
234
234
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: acp-agent.js
4
4
  resource: npm/src/core/acp-agent.js
5
5
  docgen:
6
- crc: a9f48a2c
6
+ crc: 8fd9fdb8
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.98
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: acp-kit.js
4
4
  resource: npm/src/core/acp-kit.js
5
5
  docgen:
6
- crc: e42fda83
6
+ crc: 0c38d95b
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  judgeModel: openai-codex/gpt-5.4-mini
package/src/core/scope.js CHANGED
@@ -22,7 +22,7 @@ export function classify(catalog, actorTiers, actor, toolName) {
22
22
  const tiers = actorTiers ?? DEFAULT_ACTOR_TIERS
23
23
  const tool = getTool(catalog, toolName)
24
24
  if (!tool) return 'deny'
25
- const rank = TIER_RANK[tool.tier] ?? Number.POSITIVE_INFINITY
25
+ const rank = TIER_RANK[tool.tier] ?? Infinity
26
26
  const max = tiers[actor?.kind] ?? 0
27
27
  if (rank <= max) return 'allow'
28
28
  if (tool.tier === 'destructive' && actor?.kind === 'agent') return 'approval'
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: use-acp-agent.js
4
4
  resource: npm/src/vue/use-acp-agent.js
5
5
  docgen:
6
- crc: e5d5b271
6
+ crc: 42706f67
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  score: 100
9
9
  issues: judge:inaccurate:0.97
@@ -114,14 +114,20 @@ export function useAcpAgent({
114
114
  journal,
115
115
  /**
116
116
  * @param {string} intent user prompt
117
- * @param {{modelTier?: string}} [opts] per-call overrides
117
+ * @param {{modelTier?: string, onChunk?: (snapshot: {text: string, actions: object[]}) => void}} [opts] per-call overrides; `onChunk` streams live text/tool-calls as the turn runs
118
118
  * @returns {Promise<object>} structured result envelope
119
119
  */
120
120
  request: (intent, opts = {}) => {
121
121
  if (opts.modelTier) modelTier.value = opts.modelTier
122
- return kit.request({ intent, agent: resolveSpawnArgs() })
122
+ return kit.request({ intent, agent: resolveSpawnArgs(), onChunk: opts.onChunk })
123
123
  },
124
- respond: (requestId, message) => kit.respond({ requestId, message }),
124
+ /**
125
+ * @param {string} requestId journal record id from a prior request()/respond()
126
+ * @param {string} message follow-up user message
127
+ * @param {{onChunk?: (snapshot: {text: string, actions: object[]}) => void}} [opts] `onChunk` streams live text/tool-calls as the turn runs
128
+ * @returns {Promise<object>} updated result envelope
129
+ */
130
+ respond: (requestId, message, opts = {}) => kit.respond({ requestId, message, onChunk: opts.onChunk }),
125
131
  approve: (requestId, approve) => kit.approve({ requestId, approve })
126
132
  }
127
133
  }