@blokkli/editor 2.0.0-alpha.56 → 2.0.0-alpha.58

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.
Files changed (53) hide show
  1. package/dist/module.d.mts +2 -2
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +2 -2
  4. package/dist/modules/agent/index.d.mts +1 -1
  5. package/dist/modules/agent/index.mjs +19 -19
  6. package/dist/modules/agent/runtime/app/composables/index.d.ts +3 -1
  7. package/dist/modules/agent/runtime/app/composables/index.js +1 -0
  8. package/dist/modules/agent/runtime/app/composables/useAgent.d.ts +6 -0
  9. package/dist/modules/agent/runtime/app/composables/useAgent.js +11 -0
  10. package/dist/modules/agent/runtime/app/features/agent/Container.vue +83 -88
  11. package/dist/modules/agent/runtime/app/helpers/buildPageContext.d.ts +7 -0
  12. package/dist/modules/agent/runtime/app/helpers/buildPageContext.js +81 -0
  13. package/dist/modules/agent/runtime/app/helpers/index.d.ts +15 -1
  14. package/dist/modules/agent/runtime/app/helpers/index.js +16 -0
  15. package/dist/modules/agent/runtime/app/helpers/injections.d.ts +3 -0
  16. package/dist/modules/agent/runtime/app/helpers/injections.js +3 -0
  17. package/dist/modules/agent/runtime/app/providers/agentProvider.d.ts +33 -0
  18. package/dist/modules/agent/runtime/app/providers/agentProvider.js +315 -0
  19. package/dist/modules/agent/runtime/app/providers/conversationProvider.d.ts +50 -0
  20. package/dist/modules/agent/runtime/app/providers/conversationProvider.js +263 -0
  21. package/dist/modules/agent/runtime/app/providers/planProvider.d.ts +15 -0
  22. package/dist/modules/agent/runtime/app/providers/planProvider.js +34 -0
  23. package/dist/modules/agent/runtime/app/providers/socketProvider.d.ts +17 -0
  24. package/dist/modules/agent/runtime/app/providers/socketProvider.js +110 -0
  25. package/dist/modules/agent/runtime/app/providers/toolsProvider.d.ts +44 -0
  26. package/dist/modules/agent/runtime/app/providers/toolsProvider.js +298 -0
  27. package/dist/modules/agent/runtime/app/types/index.d.ts +47 -0
  28. package/dist/modules/agent/runtime/app/types/index.js +3 -1
  29. package/dist/modules/agent/runtime/server/helpers.js +2 -1
  30. package/dist/modules/charts/index.d.mts +1 -1
  31. package/dist/modules/drupal/index.d.mts +2 -2
  32. package/dist/modules/iframes/index.d.mts +1 -1
  33. package/dist/modules/index.d.mts +1 -1
  34. package/dist/modules/readability/index.d.mts +2 -2
  35. package/dist/modules/table-of-contents/index.d.mts +2 -2
  36. package/dist/runtime/editor/components/DiffViewer/State.vue +51 -67
  37. package/dist/runtime/editor/components/EditProvider.vue +30 -10
  38. package/dist/runtime/editor/components/PreviewProvider.vue +14 -8
  39. package/dist/runtime/editor/css/output.css +1 -1
  40. package/dist/runtime/editor/events/index.d.ts +4 -3
  41. package/dist/runtime/editor/features/add-list/index.vue +2 -1
  42. package/dist/runtime/editor/features/changelog/changelog.json +8 -0
  43. package/dist/runtime/editor/features/dev-mode/index.vue +1 -10
  44. package/dist/runtime/editor/features/responsive-preview/Frame/index.vue +2 -2
  45. package/dist/runtime/editor/features/translations/index.vue +1 -0
  46. package/dist/runtime/editor/plugins/Sidebar/index.vue +9 -2
  47. package/dist/runtime/editor/providers/state.js +4 -1
  48. package/dist/shared/{editor.BTOBvmaz.d.mts → editor.Bpw1EP57.d.mts} +3 -3
  49. package/dist/shared/{editor.9vf8ZnOp.mjs → editor.FygP6XeF.mjs} +2 -2
  50. package/dist/types.d.mts +1 -1
  51. package/package.json +30 -7
  52. package/dist/modules/agent/runtime/app/composables/agentProvider.d.ts +0 -62
  53. package/dist/modules/agent/runtime/app/composables/agentProvider.js +0 -1048
@@ -0,0 +1,315 @@
1
+ import { ref, watch } from "#imports";
2
+ import { routeRoute } from "#blokkli-build/agent-client";
3
+ import { buildPageContext } from "#blokkli/agent/app/helpers/buildPageContext";
4
+ export default function agentProvider({
5
+ app,
6
+ adapter,
7
+ agentName,
8
+ socket,
9
+ conversation,
10
+ plan,
11
+ tools
12
+ }) {
13
+ const { ui } = app;
14
+ const isReady = ref(false);
15
+ const hasBeenReady = ref(false);
16
+ const isProcessing = ref(false);
17
+ const isThinking = ref(false);
18
+ let pendingPrompt = null;
19
+ let pendingInit = null;
20
+ let sentToolNames = [];
21
+ watch(isProcessing, (processing) => {
22
+ ui.setTransform(processing ? agentName : null);
23
+ });
24
+ function resetActivityFlags() {
25
+ isProcessing.value = false;
26
+ isThinking.value = false;
27
+ }
28
+ function teardown() {
29
+ isReady.value = false;
30
+ resetActivityFlags();
31
+ ui.setTransform(null);
32
+ tools.cancelPending();
33
+ }
34
+ function connect() {
35
+ socket.connect();
36
+ }
37
+ function disconnect() {
38
+ socket.disconnect();
39
+ teardown();
40
+ }
41
+ async function onSocketOpen() {
42
+ const { toolNames } = await tools.init();
43
+ sentToolNames = toolNames;
44
+ let contentSearchTabs;
45
+ if (adapter.getContentSearchTabs) {
46
+ try {
47
+ contentSearchTabs = await adapter.getContentSearchTabs();
48
+ } catch (e) {
49
+ console.warn("[blokkli agent] Failed to fetch content search tabs:", e);
50
+ }
51
+ }
52
+ if (adapter.getAgentAuthToken) {
53
+ try {
54
+ const authToken = await adapter.getAgentAuthToken();
55
+ if (!authToken) {
56
+ conversation.pushError({ errorType: "unauthorized" });
57
+ disconnect();
58
+ return;
59
+ }
60
+ const pageContext2 = await buildPageContext(app, contentSearchTabs);
61
+ tools.setPageContext(pageContext2);
62
+ pendingInit = { toolNames, pageContext: pageContext2 };
63
+ socket.send({ type: "authenticate", authToken });
64
+ return;
65
+ } catch (e) {
66
+ console.error("Failed to obtain agent auth token:", e);
67
+ }
68
+ }
69
+ const pageContext = await buildPageContext(app, contentSearchTabs);
70
+ tools.setPageContext(pageContext);
71
+ sendInit(toolNames, pageContext);
72
+ }
73
+ async function sendInit(toolNames, pageContext) {
74
+ socket.send({ type: "init", toolNames, pageContext });
75
+ isReady.value = true;
76
+ hasBeenReady.value = true;
77
+ if (adapter.agentConversations && !pendingPrompt) {
78
+ const latest = await conversation.loadLatestFromAdapter();
79
+ if (latest) {
80
+ conversation.activeConversationId.value = latest.uuid;
81
+ conversation.applyRestoredData(latest.parsed);
82
+ socket.send({
83
+ type: "restore_conversation",
84
+ state: latest.parsed.serverState
85
+ });
86
+ }
87
+ }
88
+ if (pendingPrompt) {
89
+ const queued = pendingPrompt;
90
+ pendingPrompt = null;
91
+ sendPrompt(
92
+ queued.prompt,
93
+ queued.displayPrompt,
94
+ queued.selectedUuids,
95
+ queued.attachments,
96
+ queued.autoLoadTools,
97
+ queued.autoLoadSkills,
98
+ queued.preSeededResults,
99
+ queued.autoExecuteTools
100
+ );
101
+ }
102
+ }
103
+ function handleServerMessage(data) {
104
+ switch (data.type) {
105
+ case "authenticated":
106
+ if (pendingInit) {
107
+ sendInit(pendingInit.toolNames, pendingInit.pageContext);
108
+ pendingInit = null;
109
+ }
110
+ break;
111
+ case "thinking":
112
+ conversation.setActive(null);
113
+ isThinking.value = true;
114
+ break;
115
+ case "text":
116
+ case "text_delta":
117
+ isThinking.value = false;
118
+ conversation.appendToActive(data.content);
119
+ break;
120
+ case "tool_call":
121
+ conversation.finalizeActive();
122
+ tools.dispatch(data.callId, data.tool, data.params);
123
+ break;
124
+ case "usage":
125
+ conversation.pushUsage(data.usage);
126
+ break;
127
+ case "done":
128
+ conversation.finalizeActive();
129
+ resetActivityFlags();
130
+ if (data.message) {
131
+ conversation.pushAssistantUnlessDuplicate(data.message);
132
+ }
133
+ break;
134
+ case "error":
135
+ conversation.finalizeActive();
136
+ resetActivityFlags();
137
+ if (data.detail) {
138
+ console.warn(`[blokkli agent] ${data.errorType} error:`, data.detail);
139
+ }
140
+ conversation.pushError({
141
+ errorType: data.errorType,
142
+ retryable: data.retryable
143
+ });
144
+ break;
145
+ case "server_tool_result":
146
+ conversation.finalizeActive();
147
+ conversation.pushServerTool({ tool: data.tool, label: data.label });
148
+ break;
149
+ case "plan_update":
150
+ plan.applyUpdate(data.plan);
151
+ break;
152
+ case "transcript":
153
+ conversation.setTranscript(data.transcript);
154
+ break;
155
+ case "conversation_state":
156
+ conversation.saveToAdapter(data.state);
157
+ break;
158
+ case "conversation_restored":
159
+ break;
160
+ case "conversation_restore_failed": {
161
+ console.warn(
162
+ "[blokkli agent] Conversation restore failed:",
163
+ data.reason
164
+ );
165
+ const failedId = conversation.activeConversationId.value;
166
+ conversation.clearAll();
167
+ if (failedId) {
168
+ conversation.deleteFromAdapter(failedId).catch(() => {
169
+ });
170
+ }
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ socket.setMessageHandler(handleServerMessage);
176
+ socket.setLifecycleHandlers({
177
+ onOpen: () => {
178
+ onSocketOpen();
179
+ },
180
+ onClose: teardown,
181
+ onMaxReconnects: () => {
182
+ conversation.pushError({ errorType: "connection" });
183
+ }
184
+ });
185
+ async function sendPrompt(prompt, displayPrompt, selectedUuids, attachments, autoLoadTools, autoLoadSkills, preSeededResults, autoExecuteTools) {
186
+ if (!prompt.trim() || isProcessing.value) return;
187
+ if (!isReady.value) {
188
+ pendingPrompt = {
189
+ prompt,
190
+ displayPrompt,
191
+ selectedUuids,
192
+ attachments,
193
+ autoLoadTools,
194
+ autoLoadSkills,
195
+ preSeededResults,
196
+ autoExecuteTools
197
+ };
198
+ return;
199
+ }
200
+ isProcessing.value = true;
201
+ conversation.ensureConversationId();
202
+ conversation.pushUser(displayPrompt ?? prompt, attachments);
203
+ isThinking.value = true;
204
+ const isFirstMessage = conversation.items.value.filter((v) => v.type === "user").length === 1;
205
+ const hasClientDirectives = !!(autoLoadTools?.length || autoLoadSkills?.length);
206
+ let resolvedAutoLoadTools = autoLoadTools;
207
+ let resolvedAutoLoadSkills = autoLoadSkills;
208
+ if (isFirstMessage && !hasClientDirectives && tools.pageContext.value) {
209
+ try {
210
+ const routingResult = await fetch(routeRoute, {
211
+ method: "POST",
212
+ headers: { "Content-Type": "application/json" },
213
+ body: JSON.stringify({
214
+ prompt,
215
+ toolNames: sentToolNames,
216
+ pageContext: tools.pageContext.value
217
+ })
218
+ }).then(
219
+ (r) => r.json()
220
+ );
221
+ if (routingResult.usage) {
222
+ conversation.pushUsage(routingResult.usage);
223
+ }
224
+ if (routingResult.tools?.length) {
225
+ resolvedAutoLoadTools = [
226
+ ...resolvedAutoLoadTools || [],
227
+ ...routingResult.tools
228
+ ];
229
+ }
230
+ if (routingResult.skills?.length) {
231
+ resolvedAutoLoadSkills = [
232
+ ...resolvedAutoLoadSkills || [],
233
+ ...routingResult.skills
234
+ ];
235
+ }
236
+ } catch {
237
+ }
238
+ }
239
+ const serverPreSeeded = preSeededResults?.length ? preSeededResults.map(({ toolName, params, result }) => ({
240
+ toolName,
241
+ params,
242
+ result
243
+ })) : void 0;
244
+ socket.send({
245
+ type: "start",
246
+ prompt,
247
+ selectedUuids: selectedUuids?.length ? selectedUuids : void 0,
248
+ autoLoadTools: resolvedAutoLoadTools?.length ? resolvedAutoLoadTools : void 0,
249
+ autoLoadSkills: resolvedAutoLoadSkills?.length ? resolvedAutoLoadSkills : void 0,
250
+ preSeededResults: serverPreSeeded,
251
+ autoExecuteTools: autoExecuteTools?.length ? autoExecuteTools : void 0
252
+ });
253
+ }
254
+ function retry() {
255
+ if (isProcessing.value || !isReady.value) return;
256
+ const lastUserItem = [...conversation.items.value].reverse().find((item) => item.type === "user");
257
+ if (!lastUserItem || lastUserItem.type !== "user") return;
258
+ conversation.items.value = conversation.items.value.filter(
259
+ (item) => !(item.type === "error" && "retryable" in item && item.retryable)
260
+ );
261
+ isProcessing.value = true;
262
+ socket.send({ type: "start", prompt: lastUserItem.content });
263
+ }
264
+ function cancel() {
265
+ tools.cancelPending();
266
+ conversation.setActive(null);
267
+ socket.send({ type: "cancel" });
268
+ resetActivityFlags();
269
+ conversation.pushAssistant(app.$t("aiAgentCancelled", "Cancelled"));
270
+ }
271
+ function newConversation() {
272
+ tools.cancelPending();
273
+ conversation.clearAll();
274
+ plan.clear();
275
+ resetActivityFlags();
276
+ socket.send({ type: "new_conversation" });
277
+ }
278
+ function getTranscript() {
279
+ socket.send({ type: "get_transcript" });
280
+ }
281
+ async function switchConversation(id) {
282
+ if (isProcessing.value) return;
283
+ const parsed = await conversation.loadFromAdapter(id);
284
+ if (!parsed) return;
285
+ conversation.applyRestoredData(parsed);
286
+ conversation.activeConversationId.value = id;
287
+ socket.send({ type: "restore_conversation", state: parsed.serverState });
288
+ conversation.showConversationList.value = false;
289
+ }
290
+ async function deleteConversation(id) {
291
+ await conversation.deleteFromAdapter(id);
292
+ if (conversation.activeConversationId.value === id) {
293
+ conversation.clearAll();
294
+ resetActivityFlags();
295
+ socket.send({ type: "new_conversation" });
296
+ }
297
+ await conversation.refreshList();
298
+ }
299
+ return {
300
+ isReady,
301
+ hasBeenReady,
302
+ isProcessing,
303
+ isThinking,
304
+ connect,
305
+ disconnect,
306
+ sendPrompt,
307
+ retry,
308
+ cancel,
309
+ newConversation,
310
+ getTranscript,
311
+ switchConversation,
312
+ deleteConversation,
313
+ refreshConversationList: conversation.refreshList
314
+ };
315
+ }
@@ -0,0 +1,50 @@
1
+ import { type Ref } from '#imports';
2
+ import { type ConversationItem, type ActiveItem, type ToolConversationItem, type ServerToolConversationItem, type ErrorConversationItem, type Attachment } from '#blokkli/agent/app/types';
3
+ import type { ConversationStateSnapshot, UsageTurn, Transcript } from '#blokkli/agent/shared/types';
4
+ import type { AgentConversationData, AgentConversationSummary } from '#blokkli/agent/app/features/agent/types';
5
+ import type { FullBlokkliAdapter } from '#blokkli/editor/adapter';
6
+ export type ParsedConversation = {
7
+ conversation: ConversationItem[];
8
+ usageTurns: UsageTurn[];
9
+ serverState: ConversationStateSnapshot;
10
+ feedbackItemIds: string[];
11
+ };
12
+ export type ConversationProvider = {
13
+ items: Ref<ConversationItem[]>;
14
+ activeItem: Ref<ActiveItem | null>;
15
+ usageTurns: Ref<UsageTurn[]>;
16
+ feedbackItemIds: Ref<Set<string>>;
17
+ toolDetails: Map<string, unknown>;
18
+ activeConversationId: Ref<string | null>;
19
+ conversationList: Ref<AgentConversationSummary[]>;
20
+ showConversationList: Ref<boolean>;
21
+ transcriptContent: Ref<Transcript | null>;
22
+ showTranscript: Ref<boolean>;
23
+ pushUser: (content: string, attachments?: Attachment[]) => void;
24
+ appendToActive: (text: string) => void;
25
+ setActive: (item: ActiveItem | null) => void;
26
+ finalizeActive: () => void;
27
+ pushAssistant: (content: string) => void;
28
+ pushAssistantUnlessDuplicate: (content: string) => void;
29
+ pushTool: (item: ToolConversationItem) => void;
30
+ pushServerTool: (item: Pick<ServerToolConversationItem, 'tool' | 'label'>) => void;
31
+ pushError: (item: Pick<ErrorConversationItem, 'errorType' | 'retryable'>) => void;
32
+ pushUsage: (turn: UsageTurn) => void;
33
+ setToolDetail: (callId: string, details: unknown) => void;
34
+ clearAll: () => void;
35
+ ensureConversationId: () => string;
36
+ setTranscript: (transcript: Transcript) => void;
37
+ applyRestoredData: (parsed: ParsedConversation) => void;
38
+ parseFromAdapter: (data: AgentConversationData) => ParsedConversation | null;
39
+ saveToAdapter: (serverState: ConversationStateSnapshot) => Promise<void>;
40
+ loadFromAdapter: (uuid: string) => Promise<ParsedConversation | null>;
41
+ loadLatestFromAdapter: () => Promise<{
42
+ parsed: ParsedConversation;
43
+ uuid: string;
44
+ } | null>;
45
+ deleteFromAdapter: (uuid: string) => Promise<void>;
46
+ refreshList: () => Promise<void>;
47
+ };
48
+ export default function conversationProvider({ adapter, }: {
49
+ adapter: FullBlokkliAdapter<any>;
50
+ }): ConversationProvider;
@@ -0,0 +1,263 @@
1
+ import { ref, reactive } from "#imports";
2
+ import {
3
+ conversationItemSchema
4
+ } from "#blokkli/agent/app/types";
5
+ import { generateUUID } from "#blokkli/editor/helpers/uuid";
6
+ function generateId() {
7
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
8
+ }
9
+ export default function conversationProvider({
10
+ adapter
11
+ }) {
12
+ const items = ref([]);
13
+ const activeItem = ref(null);
14
+ const usageTurns = ref([]);
15
+ const feedbackItemIds = ref(/* @__PURE__ */ new Set());
16
+ const toolDetails = reactive(/* @__PURE__ */ new Map());
17
+ const activeConversationId = ref(null);
18
+ const conversationList = ref([]);
19
+ const showConversationList = ref(false);
20
+ const transcriptContent = ref(null);
21
+ const showTranscript = ref(false);
22
+ function pushUser(content, attachments) {
23
+ items.value.push({
24
+ type: "user",
25
+ id: generateId(),
26
+ content,
27
+ timestamp: Date.now(),
28
+ attachments: attachments?.length ? attachments : void 0
29
+ });
30
+ }
31
+ function appendToActive(text) {
32
+ if (activeItem.value?.type === "assistant") {
33
+ activeItem.value = {
34
+ ...activeItem.value,
35
+ content: activeItem.value.content + text
36
+ };
37
+ } else {
38
+ activeItem.value = {
39
+ type: "assistant",
40
+ id: generateId(),
41
+ content: text,
42
+ timestamp: Date.now()
43
+ };
44
+ }
45
+ }
46
+ function setActive(item) {
47
+ activeItem.value = item;
48
+ }
49
+ function finalizeActive() {
50
+ const item = activeItem.value;
51
+ if (!item) return;
52
+ if (item.type === "assistant" && item.content) {
53
+ const finalized = {
54
+ type: "assistant",
55
+ id: item.id,
56
+ content: item.content,
57
+ timestamp: item.timestamp
58
+ };
59
+ items.value.push(finalized);
60
+ }
61
+ activeItem.value = null;
62
+ }
63
+ function pushAssistant(content) {
64
+ items.value.push({
65
+ type: "assistant",
66
+ id: generateId(),
67
+ content,
68
+ timestamp: Date.now()
69
+ });
70
+ }
71
+ function pushAssistantUnlessDuplicate(content) {
72
+ const last = items.value[items.value.length - 1];
73
+ if (last?.type === "assistant" && last.content === content) return;
74
+ pushAssistant(content);
75
+ }
76
+ function pushTool(item) {
77
+ items.value.push(item);
78
+ }
79
+ function pushServerTool(item) {
80
+ items.value.push({
81
+ type: "server_tool",
82
+ id: generateId(),
83
+ timestamp: Date.now(),
84
+ tool: item.tool,
85
+ label: item.label
86
+ });
87
+ }
88
+ function pushError(item) {
89
+ items.value.push({
90
+ type: "error",
91
+ id: generateId(),
92
+ timestamp: Date.now(),
93
+ errorType: item.errorType,
94
+ ...item.retryable ? { retryable: true } : {}
95
+ });
96
+ }
97
+ function pushUsage(turn) {
98
+ usageTurns.value = [...usageTurns.value, turn];
99
+ }
100
+ function setToolDetail(callId, details) {
101
+ toolDetails.set(callId, details);
102
+ }
103
+ function clearAll() {
104
+ items.value = [];
105
+ activeItem.value = null;
106
+ activeConversationId.value = null;
107
+ usageTurns.value = [];
108
+ feedbackItemIds.value = /* @__PURE__ */ new Set();
109
+ toolDetails.clear();
110
+ }
111
+ function ensureConversationId() {
112
+ if (!activeConversationId.value) {
113
+ activeConversationId.value = generateUUID();
114
+ }
115
+ return activeConversationId.value;
116
+ }
117
+ function setTranscript(transcript) {
118
+ transcriptContent.value = transcript;
119
+ showTranscript.value = true;
120
+ }
121
+ function parseFromAdapter(data) {
122
+ try {
123
+ const parsed = JSON.parse(data.clientState);
124
+ if (!parsed.conversation?.length) return null;
125
+ const clientConversation = parsed.conversation.map(
126
+ (item) => {
127
+ const result = conversationItemSchema.safeParse(item);
128
+ if (result.success) return result.data;
129
+ return {
130
+ type: "unknown",
131
+ id: generateId(),
132
+ timestamp: Date.now()
133
+ };
134
+ }
135
+ );
136
+ const serverParsed = JSON.parse(data.serverState);
137
+ if (!serverParsed?.messages?.length) return null;
138
+ return {
139
+ conversation: clientConversation,
140
+ usageTurns: parsed.usageTurns ?? [],
141
+ feedbackItemIds: data.feedbackItemIds ?? [],
142
+ serverState: {
143
+ messages: serverParsed.messages,
144
+ activatedLazyTools: serverParsed.activatedLazyTools,
145
+ hash: data.hash
146
+ }
147
+ };
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ function applyRestoredData(parsed) {
153
+ items.value = parsed.conversation;
154
+ usageTurns.value = parsed.usageTurns;
155
+ feedbackItemIds.value = new Set(parsed.feedbackItemIds);
156
+ activeItem.value = null;
157
+ }
158
+ async function saveToAdapter(serverState) {
159
+ if (!adapter.agentConversations) return;
160
+ if (!items.value.length) return;
161
+ const id = ensureConversationId();
162
+ const firstUser = items.value.find((item) => item.type === "user");
163
+ const titleText = firstUser && "content" in firstUser ? firstUser.content : "";
164
+ const title = titleText.length > 80 ? titleText.slice(0, 80) + "\u2026" : titleText;
165
+ try {
166
+ await adapter.agentConversations.upsert({
167
+ uuid: id,
168
+ title,
169
+ clientState: JSON.stringify({
170
+ conversation: items.value.filter((item) => item.type !== "error"),
171
+ usageTurns: usageTurns.value
172
+ }),
173
+ serverState: JSON.stringify({
174
+ messages: serverState.messages,
175
+ activatedLazyTools: serverState.activatedLazyTools
176
+ }),
177
+ hash: serverState.hash
178
+ });
179
+ } catch (e) {
180
+ console.warn("[blokkli agent] Failed to save conversation:", e);
181
+ }
182
+ }
183
+ async function loadFromAdapter(uuid) {
184
+ if (!adapter.agentConversations) return null;
185
+ try {
186
+ const data = await adapter.agentConversations.load(uuid);
187
+ if (!data) return null;
188
+ return parseFromAdapter(data);
189
+ } catch (e) {
190
+ console.warn("[blokkli agent] Failed to load conversation:", e);
191
+ return null;
192
+ }
193
+ }
194
+ async function loadLatestFromAdapter() {
195
+ if (!adapter.agentConversations) return null;
196
+ try {
197
+ const latest = await adapter.agentConversations.loadLatest();
198
+ if (!latest) return null;
199
+ const parsed = parseFromAdapter(latest);
200
+ if (!parsed) return null;
201
+ return { parsed, uuid: latest.uuid };
202
+ } catch (e) {
203
+ console.warn("[blokkli agent] Failed to load latest conversation:", e);
204
+ return null;
205
+ }
206
+ }
207
+ async function deleteFromAdapter(uuid) {
208
+ if (!adapter.agentConversations) return;
209
+ try {
210
+ await adapter.agentConversations.delete(uuid);
211
+ } catch (e) {
212
+ console.warn("[blokkli agent] Failed to delete conversation:", e);
213
+ }
214
+ }
215
+ async function refreshList() {
216
+ if (!adapter.agentConversations) {
217
+ conversationList.value = [];
218
+ return;
219
+ }
220
+ try {
221
+ const list = await adapter.agentConversations.list();
222
+ conversationList.value = list.sort(
223
+ (a, b) => b.updatedAt.localeCompare(a.updatedAt)
224
+ );
225
+ } catch (e) {
226
+ console.warn("[blokkli agent] Failed to list conversations:", e);
227
+ conversationList.value = [];
228
+ }
229
+ }
230
+ return {
231
+ items,
232
+ activeItem,
233
+ usageTurns,
234
+ feedbackItemIds,
235
+ toolDetails,
236
+ activeConversationId,
237
+ conversationList,
238
+ showConversationList,
239
+ transcriptContent,
240
+ showTranscript,
241
+ pushUser,
242
+ appendToActive,
243
+ setActive,
244
+ finalizeActive,
245
+ pushAssistant,
246
+ pushAssistantUnlessDuplicate,
247
+ pushTool,
248
+ pushServerTool,
249
+ pushError,
250
+ pushUsage,
251
+ setToolDetail,
252
+ clearAll,
253
+ ensureConversationId,
254
+ setTranscript,
255
+ applyRestoredData,
256
+ parseFromAdapter,
257
+ saveToAdapter,
258
+ loadFromAdapter,
259
+ loadLatestFromAdapter,
260
+ deleteFromAdapter,
261
+ refreshList
262
+ };
263
+ }
@@ -0,0 +1,15 @@
1
+ import { type Ref } from '#imports';
2
+ import type { ClientPlanState } from '#blokkli/agent/shared/types';
3
+ import type { SocketProvider } from './socketProvider.js';
4
+ import type { ConversationProvider } from './conversationProvider.js';
5
+ export type PlanProvider = {
6
+ plan: Ref<ClientPlanState | null>;
7
+ approve: () => void;
8
+ reject: () => void;
9
+ applyUpdate: (state: ClientPlanState | null) => void;
10
+ clear: () => void;
11
+ };
12
+ export default function planProvider({ socket, conversation, }: {
13
+ socket: SocketProvider;
14
+ conversation: ConversationProvider;
15
+ }): PlanProvider;
@@ -0,0 +1,34 @@
1
+ import { ref } from "#imports";
2
+ export default function planProvider({
3
+ socket,
4
+ conversation
5
+ }) {
6
+ const plan = ref(null);
7
+ function approve() {
8
+ socket.send({ type: "plan_approve" });
9
+ }
10
+ function reject() {
11
+ socket.send({ type: "plan_reject" });
12
+ }
13
+ function applyUpdate(state) {
14
+ if (state && state.steps.length > 0 && state.steps.every((s) => s.status === "completed")) {
15
+ conversation.pushServerTool({
16
+ tool: "plan_completed",
17
+ label: state.title
18
+ });
19
+ plan.value = null;
20
+ return;
21
+ }
22
+ plan.value = state;
23
+ }
24
+ function clear() {
25
+ plan.value = null;
26
+ }
27
+ return {
28
+ plan,
29
+ approve,
30
+ reject,
31
+ applyUpdate,
32
+ clear
33
+ };
34
+ }
@@ -0,0 +1,17 @@
1
+ import { type Ref } from '#imports';
2
+ import type { ClientMessage, ServerMessage } from '#blokkli/agent/shared/types';
3
+ export type SocketLifecycleHandlers = {
4
+ onOpen?: () => void;
5
+ onClose?: () => void;
6
+ onError?: (e: Event) => void;
7
+ onMaxReconnects?: () => void;
8
+ };
9
+ export type SocketProvider = {
10
+ isConnected: Readonly<Ref<boolean>>;
11
+ send: (msg: ClientMessage) => void;
12
+ connect: () => void;
13
+ disconnect: () => void;
14
+ setMessageHandler: (handler: (data: ServerMessage) => void) => void;
15
+ setLifecycleHandlers: (handlers: SocketLifecycleHandlers) => void;
16
+ };
17
+ export default function socketProvider(): SocketProvider;